From: Tom Smyth Subject: Re: unbound 1.26.1 To: tech Date: Sun, 20 Sep 2026 13:06:18 +0100 Thanks for doing this Stuart, some nasty RCEs in there that are fixed Much Obliged On Sun, 20 Sept 2026 at 11:12, Stuart Henderson wrote: > Index: doc/Changelog > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/Changelog,v > diff -u -p -r1.57 Changelog > --- doc/Changelog 26 May 2026 11:14:11 -0000 1.57 > +++ doc/Changelog 20 Sep 2026 09:50:48 -0000 > @@ -1,3 +1,507 @@ > +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. > + - 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. > + - 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. > + - 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. > + - 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. > + - Fix to guard access to shared ports interface array during > + set up, for analyzer. > + - 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. > + - Fix to call OPENSSL_cleanup on exit when that is defined. > + > +23 July 2026: Wouter > + - 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. > + - 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. > + - 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: > + - 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, 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 > + 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, > + 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. > + - 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. > + - 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. > + - Unit test for CVE-2026-50243. > + - Unit test for CVE-2026-50248. > + - 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. > + - iana portlist update. > + > +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. > + - 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 > + 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 > + specific records. > + > +30 June 2026: Wouter > + - Fix #1469: dohclient: DoH POST missing content-length → :status > + 400 from strict resolvers (Cloudflare, Mullvad). > + - iana portlist updated. > + > +26 June 2026: Wouter > + - Merge #1467: daemon: fix DEREF_AFTER_NULL.EX.COND on > + worker_init. This fixes error handling if the worker > + stat_timer allocation has an out of memory error. That > + makes the server not crash later, attempting to use it. > + > +24 June 2026: Wouter > + - Merge #1465 from dag-erling: Add libunbound/remote.h. Add > + a shared header containing prototypes for functions that > + both ends of a remote control connection need to implement. > + > +19 June 2026: Wouter > + - Fix for #1457: fix thread setname for thread start of > + dnstap, and fast_reload. > + - Fix to update github ci actions/checkout to v7. > + - Fix warning about file_string_matches in unbound-checkconf. > + > +17 June 2026: Wouter > + - Fix that after fast_reload the disown of the auth zone > + transfer task cleans the chunk list. Also fix the > + auth_transfer_limit test to use a forwarder for each type > + of failure, so the one is not blocked by the other waiting. > + - Fix to remove debug from auth_transfer_limit test. > + - Fix that unbound-checkconf checks if an auth-zone download > + can overwrite another file, by filename collision. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that malloc failure in auth-zone insert rr does > + not create an empty node and does not cause an infinite > + loop. Thanks to Qifan Zhang, Palo Alto Networks, for > + the report. > + - Fix that unbound-control auth_zone_reload stops the > + server answering from the zone after a failure to read. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that malloc failure in dns64_inform_super does > + not set up a half-built reply for cache store, that could > + lead to a crash. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + - Fix that malloc failure for new_local_rrset for RPZ qname > + trigger RR insert does not crash. It does not link a > + partial RRset, and logs an error on failure, and cleans > + up the dname allocation. Thanks to Qifan Zhang, Palo Alto > + Networks, for the report. > + - Fix that malloc failure in doq connection setup, does > + not crash in doq connection delete later. Thanks to Qifan > + Zhang, Palo Alto Networks, for the report. > + - Fix that malloc failure for ngtcp2_conn_server_new > + cleans up reference that older ngtcp2 versions can leave. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that on malloc failure during accept of TCP, the > + socket is not left to cause a read event loop. It uses > + slow-accept to delay accepting new connections, if > + that fails it drops the new connections. When the tcp > + connection usage is full, it waits for 50msec, to allow > + existing queries to be resolved. Thanks to Qifan Zhang, > + Palo Alto Networks, for the report. > + - Fix that malloc failure for rpz_strip_nsdname is > + checked and handled, so that it does not crash later. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that malloc failure during edns subnet addrtree > + insert is checked, so it does not crash later. Thanks to > + Qifan Zhang, Palo Alto Networks, for the report. > + - Fix to check the return value of auth_xfer_create > + during fast_reload auth-zone add and change processing. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix to check for malloc failure in rpz response create, > + for nodata and nxdomain, so it does not crash later. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that fast_reload does not terminate the server > + on malloc failure for dnstap, or if gethostname fails. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix after malloc failure for stats, then it drains the pipe > + so the internal messaging stays correct. Also it does > + not exit the server if stats pipe communication fails. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that fast_reload does not terminate the server > + on config read failure after malloc failure. Thanks to > + Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that fast_reload does not terminate the server if > + random init for DNS cookies fails. The data is only random > + generated if cookies are enabled, and the random data > + is necessary. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + > +17 June 2026: Yorgos > + - Fix memory leak on DNAME 0TTL records. > + > +16 June 2026: Wouter > + - Fix to disallow $INCLUDE for secondary zones. Start up > + of server continues if a secondary zone fails to load. > + Failed loads clear the zone data, so there is no partial > + zone. Thanks to Qifan Zhang, Palo Alto Networks, for > + the report. > + - Fix that when SVCB records cannot be written out, and > + are written in unknown format, that the zone read allows > + such unknown format SVCB records. Thanks to Qifan Zhang, > + Palo Alto Networks, for the report. > + - Fix that a half-written trust anchor file does not crash > + the server at runtime. It unlinks a wrong file from the list. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that auth-zone, and RPZ zones, do not allow out-of-zone > + records. These are records that are not under the zone apex. > + The out-of-zone records are dropped from the zone contents. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that dns64 does not ignore the `forward-no-cache` and > + `stub-no-cache` options. Thanks to Qifan Zhang, Palo Alto > + Networks, for the report. > + - Fix that a signed wildcard NSEC, is checked before use, > + so it does not allow insecure DS proofs inappropriately. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that after malloc failure a half-built local_alias does > + not crash the server. Thanks to Qifan Zhang, Palo Alto > + Networks, for the report. > + - Fix that for a zonefile only zone, if that file does not > + exist on server start, the server continues to start with > + a warning log message. Thanks to Qifan Zhang, Palo Alto > + Networks, for the report. > + - Fix that after malloc failure in RPZ load a half built > + list does not crash later. The newly created RRset is > + linked after creation has succeeded. Thanks to Qifan Zhang, > + Palo Alto Networks, for the report. > + - Fix that dnscrypt configuration does not crash, due to > + inconsistency between secret and public keys. Also > + duplicate files are skipped. Thanks to Qifan Zhang, Palo > + Alto Networks, for the report. > + - Fix locking in libunbound ub_ctx_set_event call. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that libunbound pipe functions fail with error after > + an event base is set. Thanks to Qifan Zhang, Palo Alto > + Networks, for the report. > + - Fix for neater solution to clear log thread id after > + worker init failure. Thanks to Qifan Zhang, Palo Alto > + Networks, for the report. > + - Fix incorrect cleanup after an allocation failure for > + a delegation point. Thanks to Qifan Zhang, Palo Alto > + Networks, for the report. > + - Fix that after malloc failure in find_tag_datas, the > + local_alias is cleaned up. Thanks to Qifan Zhang, Palo > + Alto Networks, for the report. > + - Fix that after shared memory cannot be created, from > + `shm-enable`, the server does not crash. Thanks to Qifan > + Zhang, Palo Alto Networks, for the report. > + - Fix incorrect cleanup after an allocation failure for > + a delegation point in a region. Thanks to Qifan Zhang, > + Palo Alto Networks, for the report. > + - Fix after malloc failure the rrset_insert_rr in > + localzone processing, during RPZ qname trigger processing, > + the RRset retains its previous data correcly. Thanks to > + Qifan Zhang, Palo Alto Networks, for the report. > + - Fix for #1462: Fix that auth primary host name lookup > + allows CNAMEs. > + > +15 June 2026: Wouter > + - Fix to add `max-transfer-size` and `max-transfer-time` that > + limit auth-zone and rpz transfer amount and time taken. > + Default is disabled. This hardens against unbounded > + transfers. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + - Fix perform a full transfer every number of incremental > + transfers, to stop increasing memory usage, for rpz > + zones. Thanks to Qifan Zhang, Palo Alto Networks, for > + the report. > + - Fix assertion failure for long HTTP header that fills > + buffer. Thanks to Qifan Zhang, Palo Alto Networks, for > + the report. > + - Fix buffer overflow when configured with lower than > + default size and http transfer. Thanks to Qifan Zhang, > + Palo Alto Networks, for the report. > + - Fix that misconfigured `iter-scrub-ns: 0` causes request > + failures. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + - Fix that fast_reload when a zonemd verification lookup > + it in progress with subnet loaded, deregisters the > + callback. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + - Fix for fast_reload that removes an auth zone while its > + lookups are in progress, for a primary name. Also after the > + change, it no longer picks up the old results. Thanks to > + Qifan Zhang, Palo Alto Networks, for the report. > + - Fix integer overflow in infra-cache-max-rtt calculation. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix erroneous DNS error report values after bogus AAAA > + query caused error information that was not cleared by > + a successful A subquery. Thanks to Qifan Zhang, Palo Alto > + Networks, for the report. > + - Fix integer overflow for very high values of > + `sock-queue-timeout`. Thanks to Qifan Zhang, Palo Alto > + Networks, for the report. > + - Fix that fast_reload does not terminate the server for > + errors in config, for key files. Thanks to Qifan Zhang, > + Palo Alto Networks, for the report. > + - Fix log of an aliased qname, to not use freed region > + memory. Thanks to Qifan Zhang, Palo Alto Networks, for > + the report. > + - Fix DNAME synthesis from cache that keeps use of 0TTL > + entries in a sliding window. It did not surpass RRSIG > + expiry. Thanks to Qifan Zhang, Palo Alto Networks, for > + the report. > + - Fix misconfigured ipsecmod hook causing path name > + similarity with other file. The ipsecmod is changed for > + exec of the hook. The ipsecmod hook, if a script, has to > + start now with a line like `#!/bin/sh`. Thanks to Qifan > + Zhang, Palo Alto Networks, for the report. > + - Fix that dns64 bypasses rpz-passthru rule during > + synthesis. This restricted more than necessary. Thanks to > + Qifan Zhang, Palo Alto Networks, for the report. > + > +12 June 2026: Wouter > + - Fix that for auth-zone and rpz zones the allow-notify > + addresses and netblocks are available from start, and > + fix the probe step skip. > + > +11 June 2026: Wouter > + - Fix for #1306: configure detects specifically the call to > + SSL_set_quic_tls_early_data_enabled and > + SSL_set_quic_early_data_enabled, so the correct one is used. > + - Fix for #1306: configure checks if the ngtcp2_crypto_ossl > + header file is available, and prints an error otherwise. > + - Fix #1437: Fix compile with OpenSSL 4.0.1. > + - Fix compile for OpenSSL 1.0.2 and before in server cleanup. > + > +10 June 2026: Wouter > + - Fix pythonmod script read for numeric overflow. > + - Fix warnings with gcc in compat/inet_pton.c. > + > +9 June 2026: Wouter > + - Fix unit test for ecs to check for malloc success. > + > +3 June 2026: Wouter > + - Fix that the processing of class responses does not have > + a heap use-after-free. That could happen if at least two > + distinct classes are configured for resolution. 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. > + - Fix negative cache to work with NSEC3 records without salt. > + Thanks to Xin Wang, Jiapeng Li, and Jiajia Liu, Northwestern > + Polytechnical University, for the report. > + - Fix parse of svcbparam ech, it had incorrect length. Thanks > + to Qifan Zhang, Palo Alto Networks for the report. > + - Fix that quotation and escaping works the same in auth-zone > + url content, as in the zonefile read. Thanks to Qifan Zhang, > + Palo Alto Networks for the report. > + - Fix ipset module to use larger domain name buffers, and > + check buffer lengths. Thanks to Qifan Zhang, Palo Alto > + Networks for the report. > + - Fix PROXYv2 header read and consume, it checks the header > + size. Thanks to Qifan Zhang, Palo Alto Networks for > + the report. > + - Fix negative cache NSEC3 nodata proof, to use the correct > + message size. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + - Fix fast_reload for when a ZONEMD lookup is in progress. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that validation canonicalization of domain names > + in rdata checks for buffer bounds. Thanks to Qifan Zhang, > + Palo Alto Networks, for the report. > + - Fix that dump_cache has a larger buffer for records, > + and it checks that an owner name does not collide with BADRR > + on the input, and changes verbosity on the log of failure in > + rrset to string. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + - Fix that dns64 cleans up the allocated message if the adjust > + routines fail, and checks if there is a reply before cache > + store, also unbound checks if A and AAAA are malformed > + for auth-zones. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + > +3 June 2026: Yorgos > + - Fix const as reported by newest compiler warnings. > + > +29 May 2026: Wouter > + - Fix header_seen detection for trust anchor files, so that it > + detects the id line. > + - iana portlist updated. > + - Update icannbundle.pem certificates in unbound-anchor. It > + has the public keys for 2009 to 2029 and for 2025 to 2045. > + - Fix unit test to check for new icannbundle.pem. > + > +28 May 2026: Wouter > + - Fix #1457: race condition causes segfault when starting > + threads. > + > +27 May 2026: Wouter > + - Fix for autotrust state-file line overflow, that can give > + hold-down bypass. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + - Fix to limit the DSNS per-label walk in the iterator. Thanks > + to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that the ratelimit is decremented on successful > + referrals. Thanks to Qifan Zhang, Palo Alto Networks, for > + the report. > + - Fix that msgencode insert_query has the correct assertion, > + for a local_alias. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + - Fix to reset the tcp-timeout before applying a load based > + reduction. Thanks to Qifan Zhang, Palo Alto Networks, for the > + report. > + - Fix to decrement the per-netblock tcp connection limits, so > + it keeps usable. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + - Fix manual to document ratelimit, that it is for target > + nameservers for a domain, and keeps queries limited. Thanks > + to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix, in depth, for respip rewrite of dns64 responses. Thanks > + to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that dns64 with subnetcache does not write ECS scoped > + answers to global cache. Thanks to Qifan Zhang, Palo Alto > + Networks, for the report. > + - Fix ipset module for name too long checks, race conditions > + on local name buffer, and for socket close race condition. > + Thanks to Qifan Zhang, Palo Alto Networks, for the report. > + - Fix that validator caps number of ANY RRsets it can > + validate, and the wait timer is shortened. Thanks to Qifan > + Zhang, Palo Alto Networks, for the report. > + - Fix analyzer warning in mesh_new_client. > + > +26 May 2026: Wouter > + - Fix for mesh new client and mesh new callback to rollback the > + added address, tcp mesh state and callback when there is a > failure > + to initialize. This fixes the mesh accounting of reply addresses. > + Thanks to Xin Wang, Jiapeng Li, and Jiajia Liu, Northwestern > + Polytechnical University, for the report > + > 20 May 2026: Wouter > - Fix CVE-2026-33278, Possible remote code execution during DNSSEC > validation. Thanks to Qifan Zhang, Palo Alto Networks, for the > report. > @@ -27,6 +531,79 @@ > 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. > + - Tag for 1.25.1 release, it contains the security fixes on 1.25.0. > + the code repository continues with in addition the previous > fixes, > + for 1.25.2. > + - Unit test for CVE-2026-33278. > + - Unit test for CVE-2026-42944. > + - Unit test for CVE-2026-42959. > + - Unit test for CVE-2026-40622. > + - Unit test for CVE-2026-42960. > + - Fix in depth for serve-expired responses from cachedb, that it > + does not store bogus. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + - Fix lame server detection, for selfpointed glue records. > + Thanks to Shuhan Zhang, Dan Li, and Baojun Liu from Tsinghua > + University for the report. > + - Fix cleaning up DoH session. The same query can be on multiple > + streams in a session. Thanks to Qifan Zhang, Palo Alto Networks, > + for the report. > + - Fix for signed same-owner CNAME and ordinary RRset responses. > + Thanks to Xin Wang and Jiajia Liu, Northwestern Polytechnical > + University, for the report. > + > +18 May 2026: Wouter > + - Fix for mixed class referrals, the resolver uses the query > + class. Thanks to Xin Wang and Jiajia Liu, Northwestern > + Polytechnical University, for the report. > + > +15 May 2026: Wouter > + - Fix man page entry for so-sndbuf, it is for responses sent out. > + - Fix val_find_DS for robustness, to check the result of > + packet_rrset_copy_region before using it. Thanks to Xin Wang > + and Jiajia Liu, Northwestern Polytechnical University, for > + the report. > + - Fix that for dns64 answers, the AAAA query is checked to be > + DNSSEC validated, when DNSSEC is enabled. This improves > + the RFC6147 conformance of Unbound. Thanks to Xin Wang > + and Jiajia Liu, Northwestern Polytechnical University, for > + the report. In addition, thanks to Qifan Zhang, Palo Alto > + Networks, for reporting it. > + - Fix for allocation-failure hardening of rrset cache wildcard > + storage and canonical NSEC owner replacement. Thanks to Xin > + Wang and Jiajia Liu, Northwestern Polytechnical University, > + for the report. > + - Fix DNSSEC validation with libnettle for noncanonical RSA > + DNSKEYs with leading zeroes for n. Thanks to Xin Wang and > + Jiajia Liu, Northwestern Polytechnical University, for > + the report. > + - Fix DNSKEY size calculation for noncanonical RSA DNSKEYs > + with leading zeroes for n. Thanks to Xin Wang and Jiajia Liu, > + Northwestern Polytechnical University, for the report. > + > +11 May 2026: Yorgos > + - Fix comment and verbose logging for EDNS fallback buffer size. > + > +8 May 2026: Wouter > + - Fix to relax assertions after the TTL 0 handling change. > + This relaxes an assertion in cachedb (it fails instead), > + and for packet_rrset_copy_region. > + > +7 May 2026: Wouter > + - Fix for Heap Out-of-Bounds Write via size_t-to-int Truncation > + in setup_if() - outside_network_create(). This fixes that > + large values for num_ports do not overflow and create > + invalid references after integer truncation. Thanks > + to Karnakar Reddy (@karnakarreddi) for the report. > + - Fix to clean up log ids after a failure to start a worker thread. > + > +1 May 2026: Wouter > + - iana portlist updated. > + > +29 April 2026: Wouter > + - tag for 1.25.0. The code repository continues with 1.25.1 in > + development. > + - Fix windows 64bit build for libssp dependency. > > 23 April 2026: Wouter > - Merge #1441: Fix buffer overrun in > Index: README.md > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/README.md,v > diff -u -p -r1.10 README.md > --- README.md 26 May 2026 11:14:10 -0000 1.10 > +++ README.md 20 Sep 2026 09:50:47 -0000 > @@ -10,7 +10,7 @@ Unbound is a validating, recursive, cach > fast and lean and incorporates modern features based on open standards. > If you > have any feedback, we would love to hear from you. Don’t hesitate to > [create an issue on Github]( > https://github.com/NLnetLabs/unbound/issues/new) > -or post a message on the [Unbound mailing list]( > https://lists.nlnetlabs.nl/mailman/listinfo/unbound-users). > +or post a message on our [community forum]( > https://community.nlnetlabs.nl/). > You can learn more about Unbound by reading our > [documentation](https://unbound.docs.nlnetlabs.nl/). > > Index: ax_pthread.m4 > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/ax_pthread.m4,v > diff -u -p -r1.3 ax_pthread.m4 > --- ax_pthread.m4 13 Jun 2024 14:30:28 -0000 1.3 > +++ ax_pthread.m4 20 Sep 2026 09:50:47 -0000 > @@ -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" > ;; > > Index: config.h.in > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/config.h.in,v > diff -u -p -r1.37 config.h.in > --- config.h.in 26 May 2026 11:14:10 -0000 1.37 > +++ config.h.in 20 Sep 2026 09:50:47 -0000 > @@ -31,6 +31,9 @@ > /* Whether daemon is deprecated */ > #undef DEPRECATED_DAEMON > > +/* Whether X509_NAME_get_text_by_NID is deprecated */ > +#undef DEPRECATED_X509_NAME_GET_TEXT_BY_NID > + > /* Deprecate RSA 1024 bit length, makes that an unsupported key */ > #undef DEPRECATE_RSA_1024 > > @@ -60,6 +63,9 @@ > /* Define to 1 if you have the header file. */ > #undef HAVE_ARPA_INET_H > > +/* Define to 1 if you have the `ASN1_STRING_get0_data' function. */ > +#undef HAVE_ASN1_STRING_GET0_DATA > + > /* Whether the C compiler accepts the "fallthrough" attribute */ > #undef HAVE_ATTR_FALLTHROUGH > > @@ -140,6 +146,10 @@ > to 0 if you don't. */ > #undef HAVE_DECL_NGTCP2_CRYPTO_ENCRYPT_CB > > +/* Define to 1 if you have the declaration of > `ngtcp2_crypto_ossl_ctx_new', > + and to 0 if you don't. */ > +#undef HAVE_DECL_NGTCP2_CRYPTO_OSSL_CTX_NEW > + > /* Define to 1 if you have the declaration of `NID_ED25519', and to 0 if > you > don't. */ > #undef HAVE_DECL_NID_ED25519 > @@ -289,6 +299,12 @@ > /* Define to 1 if you have the `FIPS_mode' function. */ > #undef HAVE_FIPS_MODE > > +/* Define to 1 if you have the `fnmatch' function. */ > +#undef HAVE_FNMATCH > + > +/* Define to 1 if you have the header file. */ > +#undef HAVE_FNMATCH_H > + > /* Define to 1 if you have the `fork' function. */ > #undef HAVE_FORK > > @@ -513,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 > > @@ -685,9 +704,16 @@ > /* Define to 1 if you have the `SSL_is_quic' function. */ > #undef HAVE_SSL_IS_QUIC > > +/* Define to 1 if you have the `SSL_set1_dnsname' function. */ > +#undef HAVE_SSL_SET1_DNSNAME > + > /* Define to 1 if you have the `SSL_set1_host' function. */ > #undef HAVE_SSL_SET1_HOST > > +/* Define to 1 if you have the `SSL_set_quic_tls_early_data_enabled' > function. > + */ > +#undef HAVE_SSL_SET_QUIC_TLS_EARLY_DATA_ENABLED > + > /* Define to 1 if you have the header file. */ > #undef HAVE_STDARG_H > > @@ -851,6 +877,12 @@ > > /* Define to 1 if you have the header file. */ > #undef HAVE_WS2TCPIP_H > + > +/* Define to 1 if you have the `X509_get_key_usage' function. */ > +#undef HAVE_X509_GET_KEY_USAGE > + > +/* Define to 1 if you have the `X509_NAME_get_text_by_NID' function. */ > +#undef HAVE_X509_NAME_GET_TEXT_BY_NID > > /* Define to 1 if you have the `X509_VERIFY_PARAM_set1_host' function. */ > #undef HAVE_X509_VERIFY_PARAM_SET1_HOST > Index: configure > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/configure,v > diff -u -p -r1.63 configure > --- configure 27 Jul 2026 14:14:39 -0000 1.63 > +++ configure 20 Sep 2026 09:50:47 -0000 > @@ -1,6 +1,6 @@ > #! /bin/sh > # Guess values for system-dependent variables and create Makefiles. > -# Generated by GNU Autoconf 2.71 for unbound 1.25.2. > +# Generated by GNU Autoconf 2.71 for unbound 1.26.1. > # > # Report bugs to https://github.com/NLnetLabs/unbound/issues>. > # > @@ -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.26.1' > +PACKAGE_STRING='unbound 1.26.1' > PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or > https://github.com/NLnetLabs/unbound/issues' > PACKAGE_URL='' > > @@ -1516,7 +1516,7 @@ if test "$ac_init_help" = "long"; then > # Omit some internal or obsolete options to make the list less imposing. > # This message is too long to be a string in the A/UX 3.1 sh. > cat <<_ACEOF > -\`configure' configures unbound 1.25.2 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]... > > @@ -1582,7 +1582,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.26.1:";; > esac > cat <<\_ACEOF > > @@ -1835,7 +1835,7 @@ fi > test -n "$ac_init_help" && exit $ac_status > if $ac_init_version; then > cat <<\_ACEOF > -unbound configure 1.25.2 > +unbound configure 1.26.1 > generated by GNU Autoconf 2.71 > > Copyright (C) 2021 Free Software Foundation, Inc. > @@ -2492,7 +2492,7 @@ cat >config.log <<_ACEOF > This file contains any messages produced by compilers while > running configure, to aid debugging if configure makes a mistake. > > -It was created by unbound $as_me 1.25.2, 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 > @@ -3254,13 +3254,13 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu > > UNBOUND_VERSION_MAJOR=1 > > -UNBOUND_VERSION_MINOR=25 > +UNBOUND_VERSION_MINOR=26 > > -UNBOUND_VERSION_MICRO=2 > +UNBOUND_VERSION_MICRO=1 > > > LIBUNBOUND_CURRENT=9 > -LIBUNBOUND_REVISION=38 > +LIBUNBOUND_REVISION=40 > LIBUNBOUND_AGE=1 > # 1.0.0 had 0:12:0 > # 1.0.1 had 0:13:0 > @@ -3366,6 +3366,8 @@ 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.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 > @@ -16761,6 +16763,13 @@ then : > printf "%s\n" "#define HAVE_GLOB_H 1" >>confdefs.h > > fi > +ac_fn_c_check_header_compile "$LINENO" "fnmatch.h" > "ac_cv_header_fnmatch_h" "$ac_includes_default > +" > +if test "x$ac_cv_header_fnmatch_h" = xyes > +then : > + printf "%s\n" "#define HAVE_FNMATCH_H 1" >>confdefs.h > + > +fi > ac_fn_c_check_header_compile "$LINENO" "grp.h" "ac_cv_header_grp_h" > "$ac_includes_default > " > if test "x$ac_cv_header_grp_h" = xyes > @@ -19162,7 +19171,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" > ;; > > @@ -21816,6 +21849,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 > @@ -21845,6 +21884,24 @@ then : > printf "%s\n" "#define HAVE_SSL_GET0_PEERNAME 1" >>confdefs.h > > fi > +ac_fn_c_check_func "$LINENO" "SSL_set1_dnsname" > "ac_cv_func_SSL_set1_dnsname" > +if test "x$ac_cv_func_SSL_set1_dnsname" = xyes > +then : > + printf "%s\n" "#define HAVE_SSL_SET1_DNSNAME 1" >>confdefs.h > + > +fi > +ac_fn_c_check_func "$LINENO" "X509_get_key_usage" > "ac_cv_func_X509_get_key_usage" > +if test "x$ac_cv_func_X509_get_key_usage" = xyes > +then : > + printf "%s\n" "#define HAVE_X509_GET_KEY_USAGE 1" >>confdefs.h > + > +fi > +ac_fn_c_check_func "$LINENO" "ASN1_STRING_get0_data" > "ac_cv_func_ASN1_STRING_get0_data" > +if test "x$ac_cv_func_ASN1_STRING_get0_data" = xyes > +then : > + printf "%s\n" "#define HAVE_ASN1_STRING_GET0_DATA 1" >>confdefs.h > + > +fi > ac_fn_c_check_func "$LINENO" "X509_VERIFY_PARAM_set1_host" > "ac_cv_func_X509_VERIFY_PARAM_set1_host" > if test "x$ac_cv_func_X509_VERIFY_PARAM_set1_host" = xyes > then : > @@ -21888,6 +21945,54 @@ then : > > fi > > +ac_fn_c_check_func "$LINENO" "X509_NAME_get_text_by_NID" > "ac_cv_func_X509_NAME_get_text_by_NID" > +if test "x$ac_cv_func_X509_NAME_get_text_by_NID" = xyes > +then : > + printf "%s\n" "#define HAVE_X509_NAME_GET_TEXT_BY_NID 1" >>confdefs.h > + > +fi > + > +if test $ac_cv_func_X509_NAME_get_text_by_NID = yes; then > + > + > +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if > X509_NAME_get_text_by_NID is deprecated" >&5 > +printf %s "checking if X509_NAME_get_text_by_NID is deprecated... " >&6; } > +cache=`echo X509_NAME_get_text_by_NID | sed 'y%.=/+-%___p_%'` > +if eval test \${cv_cc_deprecated_$cache+y} > +then : > + printf %s "(cached) " >&6 > +else $as_nop > + > +echo ' > +#include "openssl/x509.h" > +' >conftest.c > +echo 'void f(void){ > + (void)X509_NAME_get_text_by_NID(NULL, 0, NULL, 0); }' > >>conftest.c > +if test -z "`$CC $CPPFLAGS $CFLAGS -c conftest.c 2>&1 | grep -e > deprecated -e unavailable`"; then > +eval "cv_cc_deprecated_$cache=no" > +else > +eval "cv_cc_deprecated_$cache=yes" > +fi > +rm -f conftest conftest.o conftest.c > + > +fi > + > +if eval "test \"`echo '$cv_cc_deprecated_'$cache`\" = yes"; then > +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 > +printf "%s\n" "yes" >&6; } > + > +printf "%s\n" "#define DEPRECATED_X509_NAME_GET_TEXT_BY_NID 1" > >>confdefs.h > + > +: > + > +else > +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 > +printf "%s\n" "no" >&6; } > +: > + > +fi > + > +fi > LIBS="$BAKLIBS" > > ac_fn_check_decl "$LINENO" "SSL_COMP_get_compression_methods" > "ac_cv_have_decl_SSL_COMP_get_compression_methods" " > @@ -23394,6 +23499,24 @@ then : > > printf "%s\n" "#define USE_NGTCP2_CRYPTO_OSSL 1" >>confdefs.h > > + ac_fn_check_decl "$LINENO" "ngtcp2_crypto_ossl_ctx_new" > "ac_cv_have_decl_ngtcp2_crypto_ossl_ctx_new" "$ac_includes_default > + #include > + > +" "$ac_c_undeclared_builtin_options" "CFLAGS" > +if test "x$ac_cv_have_decl_ngtcp2_crypto_ossl_ctx_new" = xyes > +then : > + ac_have_decl=1 > +else $as_nop > + ac_have_decl=0 > +fi > +printf "%s\n" "#define HAVE_DECL_NGTCP2_CRYPTO_OSSL_CTX_NEW > $ac_have_decl" >>confdefs.h > +if test $ac_have_decl = 1 > +then : > + > +else $as_nop > + as_fn_error $? "No declaration of ngtcp2_crypto_ossl_ctx_new in the > ngtcp2_crypto_ossl header file. Perhaps the ngtcp2_crypto_ossl devel header > files need to be installed." "$LINENO" 5 > +fi > + > > else $as_nop > > @@ -23573,6 +23696,13 @@ else $as_nop > fi > > done > + ac_fn_c_check_func "$LINENO" "SSL_set_quic_tls_early_data_enabled" > "ac_cv_func_SSL_set_quic_tls_early_data_enabled" > +if test "x$ac_cv_func_SSL_set_quic_tls_early_data_enabled" = xyes > +then : > + printf "%s\n" "#define HAVE_SSL_SET_QUIC_TLS_EARLY_DATA_ENABLED 1" > >>confdefs.h > + > +fi > + > LIBS="$BAKLIBS" > > ac_fn_c_check_type "$LINENO" "struct ngtcp2_version_cid" > "ac_cv_type_struct_ngtcp2_version_cid" "$ac_includes_default > @@ -24536,6 +24666,12 @@ then : > printf "%s\n" "#define HAVE_GLOB 1" >>confdefs.h > > fi > +ac_fn_c_check_func "$LINENO" "fnmatch" "ac_cv_func_fnmatch" > +if test "x$ac_cv_func_fnmatch" = xyes > +then : > + printf "%s\n" "#define HAVE_FNMATCH 1" >>confdefs.h > + > +fi > ac_fn_c_check_func "$LINENO" "initgroups" "ac_cv_func_initgroups" > if test "x$ac_cv_func_initgroups" = xyes > then : > @@ -26331,7 +26467,7 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 1 > > > > -version=1.25.2 > +version=1.26.1 > > { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5 > printf %s "checking for build time... " >&6; } > @@ -26861,7 +26997,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_wri > # report actual input values of CONFIG_FILES etc. instead of their > # values after options handling. > ac_log=" > -This file was extended by unbound $as_me 1.25.2, 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 > @@ -26929,7 +27065,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac > cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 > ac_cs_config='$ac_cs_config_escaped' > ac_cs_version="\\ > -unbound config.status 1.25.2 > +unbound config.status 1.26.1 > configured by $0, generated by GNU Autoconf 2.71, > with options \\"\$ac_cs_config\\" > > Index: configure.ac > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/configure.ac,v > diff -u -p -r1.63 configure.ac > --- configure.ac 27 Jul 2026 14:14:39 -0000 1.63 > +++ configure.ac 20 Sep 2026 09:50:47 -0000 > @@ -11,15 +11,15 @@ 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_MINOR],[26]) > +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=38 > +LIBUNBOUND_REVISION=40 > LIBUNBOUND_AGE=1 > # 1.0.0 had 0:12:0 > # 1.0.1 had 0:13:0 > @@ -125,6 +125,8 @@ 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.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 > @@ -483,7 +485,7 @@ PKG_PROG_PKG_CONFIG > fi > > # Checks for header files. > -AC_CHECK_HEADERS([stdarg.h stdbool.h netinet/in.h netinet/tcp.h > sys/param.h sys/select.h sys/socket.h sys/un.h sys/uio.h sys/resource.h > arpa/inet.h syslog.h netdb.h sys/wait.h pwd.h glob.h grp.h login_cap.h > winsock2.h ws2tcpip.h endian.h sys/endian.h libkern/OSByteOrder.h sys/ipc.h > sys/shm.h ifaddrs.h poll.h],,, [AC_INCLUDES_DEFAULT]) > +AC_CHECK_HEADERS([stdarg.h stdbool.h netinet/in.h netinet/tcp.h > sys/param.h sys/select.h sys/socket.h sys/un.h sys/uio.h sys/resource.h > arpa/inet.h syslog.h netdb.h sys/wait.h pwd.h glob.h fnmatch.h grp.h > login_cap.h winsock2.h ws2tcpip.h endian.h sys/endian.h > libkern/OSByteOrder.h sys/ipc.h sys/shm.h ifaddrs.h poll.h],,, > [AC_INCLUDES_DEFAULT]) > # net/if.h portability for Darwin see: > # > https://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Header-Portability.html > AC_CHECK_HEADERS([net/if.h],,, [ > @@ -1080,12 +1082,19 @@ 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" > LIBS="-lssl $LIBS" > -AC_CHECK_FUNCS([OPENSSL_init_ssl SSL_CTX_set_security_level SSL_set1_host > SSL_get0_peername X509_VERIFY_PARAM_set1_host SSL_CTX_set_ciphersuites > SSL_CTX_set_tlsext_ticket_key_evp_cb SSL_CTX_set_alpn_select_cb > SSL_get0_alpn_selected SSL_CTX_set_alpn_protos SSL_get1_peer_certificate]) > +AC_CHECK_FUNCS([OPENSSL_init_ssl SSL_CTX_set_security_level SSL_set1_host > SSL_get0_peername SSL_set1_dnsname X509_get_key_usage ASN1_STRING_get0_data > X509_VERIFY_PARAM_set1_host SSL_CTX_set_ciphersuites > SSL_CTX_set_tlsext_ticket_key_evp_cb SSL_CTX_set_alpn_select_cb > SSL_get0_alpn_selected SSL_CTX_set_alpn_protos SSL_get1_peer_certificate]) > +AC_CHECK_FUNCS([X509_NAME_get_text_by_NID]) > +if test $ac_cv_func_X509_NAME_get_text_by_NID = yes; then > + ACX_FUNC_DEPRECATED([X509_NAME_get_text_by_NID], [ > + (void)X509_NAME_get_text_by_NID(NULL, 0, NULL, 0);], [ > +#include "openssl/x509.h" > +]) > +fi > LIBS="$BAKLIBS" > > AC_CHECK_DECLS([SSL_COMP_get_compression_methods,sk_SSL_COMP_pop_free,SSL_CTX_set_ecdh_auto,SSL_CTX_set_tmp_ecdh], > [], [], [ > @@ -1704,6 +1713,9 @@ if test x_$withval = x_yes -o x_$withval > AC_CHECK_LIB([ngtcp2_crypto_ossl], [ngtcp2_crypto_encrypt_cb], [ > LIBS="$LIBS -lngtcp2_crypto_ossl" > AC_DEFINE(USE_NGTCP2_CRYPTO_OSSL, 1, [Define this to use > ngtcp2_crypto_ossl.]) > + AC_CHECK_DECLS([ngtcp2_crypto_ossl_ctx_new], [], > [AC_MSG_ERROR([No declaration of ngtcp2_crypto_ossl_ctx_new in the > ngtcp2_crypto_ossl header file. Perhaps the ngtcp2_crypto_ossl devel header > files need to be installed.])], [AC_INCLUDES_DEFAULT > + #include > + ]) > ], [ > AC_CHECK_LIB([ngtcp2_crypto_openssl], [ngtcp2_crypto_encrypt_cb], > [ LIBS="$LIBS -lngtcp2_crypto_openssl" ], [ > AC_CHECK_LIB([ngtcp2_crypto_quictls], > [ngtcp2_crypto_encrypt_cb], [ LIBS="$LIBS -lngtcp2_crypto_quictls" ]) > @@ -1715,6 +1727,7 @@ if test x_$withval = x_yes -o x_$withval > BAKLIBS="$LIBS" > LIBS="-lssl $LIBS" > AC_CHECK_FUNCS([SSL_is_quic], [], [AC_MSG_ERROR([No QUIC support > detected in OpenSSL. Need OpenSSL version with QUIC support to enable DNS > over QUIC with libngtcp2.])]) > + AC_CHECK_FUNCS([SSL_set_quic_tls_early_data_enabled]) > LIBS="$BAKLIBS" > > AC_CHECK_TYPES([struct ngtcp2_version_cid, > ngtcp2_encryption_level],,,[AC_INCLUDES_DEFAULT > @@ -1928,7 +1941,7 @@ AC_LINK_IFELSE([AC_LANG_PROGRAM([ > AC_MSG_RESULT(no)) > > AC_SEARCH_LIBS([setusercontext], [util]) > -AC_CHECK_FUNCS([tzset sigprocmask fcntl getpwnam endpwent getrlimit > setrlimit setsid chroot kill chown sleep usleep random srandom recvmsg > sendmsg writev socketpair glob initgroups strftime localtime_r > setusercontext _beginthreadex endservent endprotoent fsync shmget accept4 > getifaddrs if_nametoindex poll gettid]) > +AC_CHECK_FUNCS([tzset sigprocmask fcntl getpwnam endpwent getrlimit > setrlimit setsid chroot kill chown sleep usleep random srandom recvmsg > sendmsg writev socketpair glob fnmatch initgroups strftime localtime_r > setusercontext _beginthreadex endservent endprotoent fsync shmget accept4 > getifaddrs if_nametoindex poll gettid]) > AC_CHECK_FUNCS([setresuid],,[AC_CHECK_FUNCS([setreuid])]) > AC_CHECK_FUNCS([setresgid],,[AC_CHECK_FUNCS([setregid])]) > > Index: cachedb/cachedb.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/cachedb/cachedb.c,v > diff -u -p -r1.23 cachedb.c > --- cachedb/cachedb.c 26 May 2026 11:14:11 -0000 1.23 > +++ cachedb/cachedb.c 20 Sep 2026 09:50:47 -0000 > @@ -401,6 +401,12 @@ prep_data(struct module_qstate* qstate, > FLAGS_GET_RCODE(qstate->return_msg->rep->flags) != > LDNS_RCODE_YXDOMAIN) > return 0; > + /* Do not persist data the validator has not yet seen, or has > rejected. > + * Otherwise an expired blob could maybe reach clients via > + * serve-expired. */ > + if(qstate->env->need_to_validate && > + qstate->return_msg->rep->security == sec_status_bogus) > + return 0; > /* We don't store the reply if its TTL is 0. This is probably > coming > * from upstream and it is not meant to be stored. */ > if(qstate->return_msg->rep->ttl == 0) > @@ -863,6 +869,11 @@ cachedb_handle_query(struct module_qstat > return; > } > /* No 0TTL answers escaping from external cache. */ > + if(qstate->return_msg->rep->ttl == 0) { > + qstate->return_msg = NULL; > + qstate->ext_state[id] = module_wait_module; > + return; > + } > log_assert(qstate->return_msg->rep->ttl > 0); > qstate->is_cachedb_answer = 1; > /* we are done with the query */ > Index: daemon/cachedump.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/daemon/cachedump.c,v > diff -u -p -r1.13 cachedump.c > --- daemon/cachedump.c 26 Sep 2025 07:32:37 -0000 1.13 > +++ daemon/cachedump.c 20 Sep 2026 09:50:47 -0000 > @@ -99,7 +99,7 @@ static void > dump_rrset_line(struct config_strlist_head* txt, struct > ub_packed_rrset_key* k, > time_t now, size_t i) > { > - char s[65535]; > + char s[65535*4+2048]; > if(!packed_rr_to_string(k, i, now, s, sizeof(s))) { > spool_txt_string(txt, "BADRR\n"); > return; > @@ -455,7 +455,7 @@ load_rr(RES* ssl, sldns_buffer* buf, str > /* read the line */ > if(!ssl_read_buf(ssl, buf)) > return 0; > - if(strncmp((char*)sldns_buffer_begin(buf), "BADRR\n", 6) == 0) { > + if(strcmp((char*)sldns_buffer_begin(buf), "BADRR") == 0) { > *go_on = 0; > return 1; > } > Index: daemon/daemon.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/daemon/daemon.c,v > diff -u -p -r1.30 daemon.c > --- daemon/daemon.c 27 Jul 2026 14:14:39 -0000 1.30 > +++ daemon/daemon.c 20 Sep 2026 09:50:47 -0000 > @@ -217,7 +217,8 @@ setup_listen_sslctx(void** ctx, int is_d > (cfg->tls_session_ticket_keys.first && > cfg->tls_session_ticket_keys.first->str[0] != 0), > is_dot, is_doh, cfg->tls_protocols))) { > - fatal_exit("could not set up listen SSL_CTX"); > + log_err("could not set up listen SSL_CTX"); > + *ctx = NULL; > } > } > #endif /* HAVE_SSL */ > @@ -259,7 +260,8 @@ void* daemon_setup_listen_quic_sslctx(st > pem += strlen(chroot); > > if(!(ctx = quic_sslctx_create(key, pem, NULL))) { > - fatal_exit("could not set up quic SSL_CTX"); > + log_err("could not set up quic SSL_CTX"); > + return NULL; > } > return ctx; > } > @@ -277,8 +279,10 @@ void* daemon_setup_connect_dot_sslctx(st > bundle += strlen(chroot); > > if(!(ctx = connect_sslctx_create(NULL, NULL, bundle, > - cfg->tls_win_cert))) > - fatal_exit("could not set up connect SSL_CTX"); > + cfg->tls_win_cert))) { > + log_err("could not set up connect SSL_CTX"); > + return NULL; > + } > return ctx; > } > #endif /* HAVE_SSL */ > @@ -308,16 +312,22 @@ daemon_setup_sslctxs(struct daemon* daem > } > daemon->listen_dot_sslctx = daemon_setup_listen_dot_sslctx( > daemon, cfg); > + if(!daemon->listen_dot_sslctx) > + fatal_exit("Could not set up listen dot sslctx"); > #ifdef HAVE_NGHTTP2_NGHTTP2_H > if(cfg_has_https(cfg)) { > daemon->listen_doh_sslctx = > daemon_setup_listen_doh_sslctx(daemon, > cfg); > + if(!daemon->listen_doh_sslctx) > + fatal_exit("Could not set up listen doh > sslctx"); > } > #endif > #ifdef HAVE_NGTCP2 > if(cfg_has_quic(cfg)) { > daemon->listen_quic_sslctx = > daemon_setup_listen_quic_sslctx(daemon, > cfg); > + if(!daemon->listen_quic_sslctx) > + fatal_exit("Could not set up listen quic > sslctx"); > } > #endif /* HAVE_NGTCP2 */ > > @@ -350,6 +360,8 @@ daemon_setup_sslctxs(struct daemon* daem > } > daemon->connect_dot_sslctx = daemon_setup_connect_dot_sslctx( > daemon, cfg); > + if(!daemon->connect_dot_sslctx) > + fatal_exit("could not setup connect dot sslctx"); > #else /* HAVE_SSL */ > (void)daemon;(void)cfg; > #endif /* HAVE_SSL */ > @@ -601,7 +613,7 @@ int setup_acl_for_ports(struct acl_list* > return 1; > } > > -int > +int > daemon_open_shared_ports(struct daemon* daemon) > { > log_assert(daemon); > @@ -921,13 +933,14 @@ thread_start(void* arg) > { > struct worker* worker = (struct worker*)arg; > int port_num = 0; > - log_assert(worker->thr_id); > set_log_thread_id(worker, worker->daemon->cfg); > { > char name[16]; /* seems to be the safest size between > different OSes */ > snprintf(name, sizeof(name), "unbound/%u", > worker->thread_num); > - ub_thread_setname(worker->thr_id, name); > + /* worker->thr_id can be written to after the thread was > made > + * by the creating thread, so this uses pthread_self. */ > + ub_thread_setname(ub_thread_self(), name); > } > ub_thread_blocksigs(); > #ifdef THREADS_DISABLED > @@ -942,8 +955,9 @@ thread_start(void* arg) > port_num = 0; > #endif > if(!worker_init(worker, worker->daemon->cfg, > - worker->daemon->ports[port_num], 0)) > + worker->daemon->ports[port_num], 0)) { > fatal_exit("Could not initialize thread"); > + } > > worker_work(worker); > return NULL; > @@ -1105,8 +1119,9 @@ daemon_fork(struct daemon* daemon) > > #if defined(HAVE_EV_LOOP) || defined(HAVE_EV_DEFAULT_LOOP) > /* in libev the first inited base gets signals */ > - if(!worker_init(daemon->workers[0], daemon->cfg, daemon->ports[0], > 1)) > + if(!worker_init(daemon->workers[0], daemon->cfg, daemon->ports[0], > 1)) { > fatal_exit("Could not initialize main thread"); > + } > #endif > > /* Now create the threads and init the workers. > @@ -1119,8 +1134,9 @@ daemon_fork(struct daemon* daemon) > */ > #if !(defined(HAVE_EV_LOOP) || defined(HAVE_EV_DEFAULT_LOOP)) > /* libevent has the last inited base get signals (or any base) */ > - if(!worker_init(daemon->workers[0], daemon->cfg, daemon->ports[0], > 1)) > + if(!worker_init(daemon->workers[0], daemon->cfg, daemon->ports[0], > 1)) { > fatal_exit("Could not initialize main thread"); > + } > #endif > signal_handling_playback(daemon->workers[0]); > > @@ -1164,7 +1180,6 @@ daemon_cleanup(struct daemon* daemon) > /* before stopping main worker, handle signals ourselves, so we > don't die on multiple reload signals for example. */ > signal_handling_record(); > - log_thread_set(NULL); > /* clean up caches because > * a) RRset IDs will be recycled after a reload, causing collisions > * b) validation config can change, thus rrset, msg, keycache clear > @@ -1270,7 +1285,7 @@ daemon_delete(struct daemon* daemon) > # if HAVE_DECL_SSL_COMP_GET_COMPRESSION_METHODS && > HAVE_DECL_SK_SSL_COMP_POP_FREE > # ifndef S_SPLINT_S > # if OPENSSL_VERSION_NUMBER < 0x10100000 > - sk_SSL_COMP_pop_free(comp_meth, (void(*)())CRYPTO_free); > + sk_SSL_COMP_pop_free(comp_meth, (void(*)(SSL_COMP*))CRYPTO_free); > # endif > # endif > # endif > @@ -1293,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 > Index: daemon/remote.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/daemon/remote.c,v > diff -u -p -r1.46 remote.c > --- daemon/remote.c 27 Jul 2026 14:14:39 -0000 1.46 > +++ daemon/remote.c 20 Sep 2026 09:50:47 -0000 > @@ -307,7 +307,7 @@ add_open(const char* ip, int nr, struct > #endif > } > } else { > - char* s = strchr(ip, '@'); > + const char* s = strchr(ip, '@'); > char newif[128]; > if(s) { > /* override port with ifspec@port */ > @@ -1533,18 +1533,95 @@ do_datas_add(struct daemon_remote* rc, R > (void)ssl_printf(ssl, "added %d datas\n", num); > } > > +static int > +perform_data_remove_rr(RES* ssl, struct local_zones* local_zones, > + uint8_t* rr, size_t len, size_t dname_len, char *arg) > +{ > + uint16_t rr_class, rr_type; > + int labs; > + struct local_zone* z; > + struct local_data* ld; > + uint8_t *rdata; > + size_t rdata_len, index; > + struct packed_rrset_data* d; > + struct local_rrset* p; > + > + rdata = sldns_wirerr_get_rdatawl(rr, len, dname_len); > + rdata_len = ((size_t)sldns_wirerr_get_rdatalen(rr, len, > dname_len))+2; > + > + labs = dname_count_labels(rr); > + > + rr_class = sldns_wirerr_get_class(rr, len, dname_len); > + rr_type = sldns_wirerr_get_type(rr, len, dname_len); > + > + z = local_zones_lookup(local_zones, rr, dname_len, > + labs, rr_class, rr_type, 1); > + if (!z) { > + ssl_printf(ssl, "error no zone for rr %s\n", arg); > + return 0; > + } > + > + ld = local_zone_find_data(z, rr, dname_len, labs); > + if (!ld) { > + ssl_printf(ssl, "error no local data for rr %s\n", arg); > + return 0; > + } > + > + p = ld->rrsets; > + while (p && ntohs(p->rrset->rk.type) != rr_type) { > + p = p->next; > + } > + > + if (!p) { > + ssl_printf(ssl, "error no rrset for rr %s\n", arg); > + return 0; > + } > + > + d = (struct packed_rrset_data*)p->rrset->entry.data; > + if (!packed_rrset_find_rr(d, rdata, rdata_len, &index)) { > + ssl_printf(ssl, "error rr %s not found in rrset\n", arg); > + return 0; > + } > + > + if (!local_rrset_remove_rr(d, index)) { > + ssl_printf(ssl, "error unable to delete rr %s\n", arg); > + return 0; > + } > + > + return 1; > +} > + > /** Remove RR data */ > static int > perform_data_remove(RES* ssl, struct local_zones* zones, char* arg) > { > - uint8_t* nm; > - int nmlabs; > - size_t nmlen; > - if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) > + uint8_t rr[LDNS_RR_BUF_SIZE], *nm; > + size_t len = sizeof(rr); > + int status, nmlabs; > + size_t nmlen, dname_len; > + > + /* try to parse as a rr first */ > + status = sldns_str2wire_rr_buf(arg, rr, &len, &dname_len, 3600, > + NULL, 0, NULL, 0); > + > + /* try to parse as a domain name second */ > + if (status != 0) { > + if (parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) { > + local_zones_del_data(zones, nm, > + nmlen, nmlabs, LDNS_RR_CLASS_IN); > + free(nm); > + return 1; > + } > + ssl_printf(ssl, "error cannot parse rr %s at %d: %s\n", > arg, > + LDNS_WIREPARSE_OFFSET(status), > + sldns_get_errorstr_parse(status)); > return 0; > - local_zones_del_data(zones, nm, > - nmlen, nmlabs, LDNS_RR_CLASS_IN); > - free(nm); > + } > + > + /* handle the rr case */ > + if (!perform_data_remove_rr(ssl, zones, rr, len, dname_len, arg)) > + return 0; > + > return 1; > } > > @@ -2315,6 +2392,9 @@ zone_del_rrset(struct lruhash_entry* e, > (struct packed_rrset_data*)e->data; > if(d->ttl > inf->expired) { > d->ttl = inf->expired; > + if(d->ttl_add > inf->expired) > + d->ttl_add = inf->expired; /* for 0TTL > rrsets, > + means that d->ttl_add <= d->ttl */ > inf->num_rrsets++; > } > } > @@ -3238,6 +3318,10 @@ do_auth_zone_reload(RES* ssl, struct wor > return; > } > if(!auth_zone_read_zonefile(z, worker->env.cfg)) { > + /* The old tree was already cleared. Do not answer from the > + * failed load. */ > + z->zone_expired = 1; > + auth_zone_clear_data(z); > lock_rw_unlock(&z->lock); > if(xfr) { > lock_basic_unlock(&xfr->lock); > @@ -3249,6 +3333,7 @@ do_auth_zone_reload(RES* ssl, struct wor > z->zone_expired = 0; > if(xfr) { > xfr->zone_expired = 0; > + xfr->num_ixfrs = 0; > if(!xfr_find_soa(z, xfr)) { > if(z->data.count == 0) { > lock_rw_unlock(&z->lock); > @@ -4941,6 +5026,74 @@ fr_check_changed_cfg_str2list(struct con > } > } > > +/** 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) > @@ -5477,6 +5630,23 @@ xfr_masterlist_equal(struct auth_master* > return 0; > } > > +/** See if configuration has changed. */ > +static int > +xfr_config_equal(struct auth_xfer* xfr1, struct auth_xfer* xfr2) > +{ > + if(xfr1 == NULL && xfr2 == NULL) > + return 1; > + if(xfr1 == NULL && xfr2 != NULL) > + return 0; > + if(xfr1 != NULL && xfr2 == NULL) > + return 0; > + if(xfr1->max_transfer_size != xfr2->max_transfer_size) > + return 0; > + if(xfr1->max_transfer_time != xfr2->max_transfer_time) > + return 0; > + return 1; > +} > + > /** See if the list of masters has changed. */ > static int > xfr_masters_equal(struct auth_xfer* xfr1, struct auth_xfer* xfr2) > @@ -5565,8 +5735,31 @@ auth_zones_check_changes(struct fast_rel > &old_serial)!=0); > have_new = (auth_zone_get_serial(new_z, > &new_serial)!=0); > + /* A change in primaries, also means it is > different > + * and the change makes it fire new transfers, from > + * the new primaries. */ > + /* Treat as changed when the old zone has an > + * outstanding ZONEMD DS/DNSKEY mesh callback. > + * This will make the worker pickup change code > + * remove the mesh callback, before the old zone is > + * deleted. Also it makes a new zonemd lookup. > + * The new lookup is needed, because the new zone > + * entry needs to have a valid zonemd result, > + * and if that is bad, needs to be invalidated. > + * Also if there is a race event where the > + * outstanding callback makes the zone invalid, > + * before fast-reload completes, the change makes > + * the new zone entry have a new zonemd lookup, > + * to then invalidate that new zone. > + * There is also a brief operational window at > + * program start when a zonemd has to be looked > + * up on-line, where the zone is operational. > + * And this copies that for such a race event. > + */ > if(have_old != have_new || old_serial != new_serial > - || !xfr_masters_equal(old_xfr, new_xfr)) { > + || !xfr_masters_equal(old_xfr, new_xfr) > + || !xfr_config_equal(old_xfr, new_xfr) > + || old_z->zonemd_callback_env != NULL) { > /* The zone has been changed. */ > if(!fr_add_auth_zone_change(fr, old_z, > new_z, > 0, 0, 1)) { > @@ -5639,6 +5832,8 @@ ct_create_sslctxs(struct fast_reload_con > /* Leave listen ctxs and file str at NULL */ > ct->connect_dot_sslctx = daemon_setup_connect_dot_sslctx( > daemon, newcfg); > + if(!ct->connect_dot_sslctx) > + return 0; > return 1; > } > > @@ -5648,20 +5843,28 @@ ct_create_sslctxs(struct fast_reload_con > pem += strlen(chroot); > > ct->listen_dot_sslctx = daemon_setup_listen_dot_sslctx(daemon, > newcfg); > + if(!ct->listen_dot_sslctx) > + return 0; > #ifdef HAVE_NGHTTP2_NGHTTP2_H > if(cfg_has_https(newcfg)) { > ct->listen_doh_sslctx = daemon_setup_listen_doh_sslctx( > daemon, newcfg); > + if(!ct->listen_doh_sslctx) > + return 0; > } > #endif > #ifdef HAVE_NGTCP2 > if(cfg_has_quic(newcfg)) { > ct->listen_quic_sslctx = daemon_setup_listen_quic_sslctx( > daemon, newcfg); > + if(!ct->listen_quic_sslctx) > + return 0; > } > #endif /* HAVE_NGTCP2 */ > ct->connect_dot_sslctx = daemon_setup_connect_dot_sslctx(daemon, > newcfg); > + if(!ct->connect_dot_sslctx) > + return 0; > > /* Store mtime and names */ > ct->ssl_service_key = strdup(newcfg->ssl_service_key); > @@ -6387,6 +6590,8 @@ fr_atomic_copy_cfg(struct config_file* o > COPY_VAR_ptr(ipset_name_v6); > #endif > COPY_VAR_int(ede); > + COPY_VAR_int(val_validation_attempts); > + COPY_VAR_int(val_hash_attempts); > COPY_VAR_int(iter_scrub_ns); > COPY_VAR_int(iter_scrub_cname); > COPY_VAR_int(iter_scrub_rrsig); > @@ -6631,9 +6836,12 @@ fr_reload_config(struct fast_reload_thre > } > #ifdef USE_DNSTAP > if(env->cfg->dnstap) { > - if(!fr->fr_nopause) > - dt_apply_cfg(daemon->dtenv, env->cfg); > - else dt_apply_logcfg(daemon->dtenv, env->cfg); > + if(!fr->fr_nopause) { > + if(!dt_apply_cfg(daemon->dtenv, env->cfg)) > + log_warn("fast_reload: dnstap > identity/version metadata not updated due to allocation failure"); > + } else { > + dt_apply_logcfg(daemon->dtenv, env->cfg); > + } > } > #endif > fr_adjust_cache(env, ct->oldcfg); > @@ -6773,6 +6981,10 @@ fr_load_config(struct fast_reload_thread > 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; > @@ -6864,7 +7076,7 @@ static void* fast_reload_thread_main(voi > #endif > log_thread_set(&fast_reload_thread->threadnum); > > - ub_thread_setname(fast_reload_thread->tid, name); > + ub_thread_setname(ub_thread_self(), name); > (void)name; /* When setname is not defined, ignore the name > variable. */ > > verbose(VERB_ALGO, "start fast reload thread"); > @@ -7587,7 +7799,8 @@ auth_zone_zonemd_stop_lookup(struct auth > qinfo.local_alias = NULL; > > mesh_remove_callback(mesh, &qinfo, qflags, > - &auth_zonemd_dnskey_lookup_callback, z); > + &auth_zonemd_dnskey_lookup_callback, z, > + z->zonemd_callback_unique_info); > } > > /** Pick up the auth zone locks. */ > @@ -7696,6 +7909,9 @@ auth_xfr_pickup_config(struct auth_xfer* > log_assert(loadxfr->namelabs == xfr->namelabs); > log_assert(loadxfr->dclass == xfr->dclass); > > + xfr->max_transfer_size = loadxfr->max_transfer_size; > + xfr->max_transfer_time = loadxfr->max_transfer_time; > + > /* The lists can be swapped in, the other xfr struct will be > deleted > * afterwards. */ > probe_masters = xfr->task_probe->masters; > @@ -7720,6 +7936,16 @@ fr_worker_auth_add(struct worker* worker > /* The xfr item needs to be created. The auth zones lock > * is held to make this possible. */ > xfr = auth_xfer_create(worker->env.auth_zones, > item->new_z); > + if(!xfr) { > + log_err("out of memory in fr_worker_auth_add"); > + lock_rw_unlock(&item->new_z->lock); > + lock_rw_unlock(&worker->env.auth_zones->lock); > + > lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock); > + if(loadxfr) { > + lock_basic_unlock(&loadxfr->lock); > + } > + return; > + } > auth_xfr_pickup_config(loadxfr, xfr); > /* Serial information is copied into the xfr struct. */ > if(!xfr_find_soa(item->new_z, xfr)) { > @@ -7789,6 +8015,17 @@ fr_worker_auth_cha(struct worker* worker > } else if(loadxfr && !xfr) { > /* Create the xfr. */ > xfr = auth_xfer_create(worker->env.auth_zones, > item->new_z); > + if(!xfr) { > + log_err("out of memory in fr_worker_auth_cha"); > + lock_rw_unlock(&item->new_z->lock); > + lock_rw_unlock(&item->old_z->lock); > + > lock_rw_unlock(&worker->daemon->fast_reload_thread->old_auth_zones->lock); > + lock_rw_unlock(&worker->env.auth_zones->lock); > + if(loadxfr) { > + lock_basic_unlock(&loadxfr->lock); > + } > + return; > + } > auth_xfr_pickup_config(loadxfr, xfr); > item->new_z->zone_is_slave = 1; > } > Index: daemon/remote.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/daemon/remote.h,v > diff -u -p -r1.11 remote.h > --- daemon/remote.h 26 May 2026 11:14:11 -0000 1.11 > +++ daemon/remote.h 20 Sep 2026 09:50:47 -0000 > @@ -49,6 +49,7 @@ > #include > #endif > #include "util/locks.h" > +#include "libunbound/remote.h" > struct config_file; > struct listen_list; > struct listen_port; > @@ -364,13 +365,6 @@ void fast_reload_thread_start(RES* ssl, > * @param fast_reload_thread: the thread struct. > */ > void fast_reload_thread_stop(struct fast_reload_thread* > fast_reload_thread); > - > -/** fast reload thread commands to remote service thread event callback */ > -void fast_reload_service_cb(int fd, short bits, void* arg); > - > -/** fast reload callback for the remote control client connection */ > -int fast_reload_client_callback(struct comm_point* c, void* arg, int err, > - struct comm_reply* rep); > > /** fast reload printq delete list */ > void fast_reload_printq_list_delete(struct fast_reload_printq* list); > Index: daemon/stats.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/daemon/stats.c,v > diff -u -p -r1.20 stats.c > --- daemon/stats.c 26 May 2026 11:14:11 -0000 1.20 > +++ daemon/stats.c 20 Sep 2026 09:50:47 -0000 > @@ -422,12 +422,28 @@ void server_stats_obtain(struct worker* > # endif > #endif > ); > + log_err("server_stats_obtain: no response from worker %d " > + "(stats timeout); returning zero stats for this > worker", > + who->thread_num); > + /* A later reply from the worker, would be sizeof stats > reply, > + * and the worker_handle_control_cmd routine discards if > + * it is not a 4byte command, when that is received here. > */ > + memset(s, 0, sizeof(*s)); > + return; > + } > + if(!tube_read_msg(worker->cmd, &reply, &len, 0)) { > + log_err("server_stats_obtain: failed to read stats from > worker " > + "(tube read error); returning zero stats for this > worker"); > + memset(s, 0, sizeof(*s)); > + return; > + } > + if(len != (uint32_t)sizeof(*s)) { > + log_err("server_stats_obtain: wrong stats length %d > (expected %d); " > + "discarding", (int)len, (int)sizeof(*s)); > + free(reply); > + memset(s, 0, sizeof(*s)); > + return; > } > - if(!tube_read_msg(worker->cmd, &reply, &len, 0)) > - fatal_exit("failed to read stats over cmd channel"); > - if(len != (uint32_t)sizeof(*s)) > - fatal_exit("stats on cmd channel wrong length %d %d", > - (int)len, (int)sizeof(*s)); > memcpy(s, reply, (size_t)len); > free(reply); > } > @@ -439,7 +455,7 @@ void server_stats_reply(struct worker* w > verbose(VERB_ALGO, "write stats replymsg"); > if(!tube_write_msg(worker->daemon->workers[0]->cmd, > (uint8_t*)&s, sizeof(s), 0)) > - fatal_exit("could not write stat values over cmd channel"); > + log_err("could not write stat values over cmd channel"); > } > > void server_stats_add(struct ub_stats_info* total, struct ub_stats_info* > a) > Index: daemon/worker.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/daemon/worker.c,v > diff -u -p -r1.46 worker.c > --- daemon/worker.c 27 Jul 2026 14:14:39 -0000 1.46 > +++ daemon/worker.c 20 Sep 2026 09:50:47 -0000 > @@ -501,7 +501,9 @@ worker_handle_control_cmd(struct tube* A > return; > } > if(len != sizeof(uint32_t)) { > - fatal_exit("bad control msg length %d", (int)len); > + verbose(VERB_ALGO, "bad control msg length %d", (int)len); > + free(msg); > + return; > } > cmd = sldns_read_uint32(msg); > free(msg); > @@ -714,7 +716,8 @@ apply_respip_action(struct worker* worke > struct respip_client_info* cinfo, struct reply_info* rep, > struct sockaddr_storage* addr, socklen_t addrlen, > struct ub_packed_rrset_key** alias_rrset, > - struct reply_info** encode_repp, struct auth_zones* az) > + struct reply_info** encode_repp, struct auth_zones* az, > + int* rpz_passthru) > { > struct respip_action_info actinfo = {0, 0, 0, 0, NULL, 0, NULL}; > actinfo.action = respip_none; > @@ -725,7 +728,7 @@ apply_respip_action(struct worker* worke > return 1; > > if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, &actinfo, > - alias_rrset, 0, worker->scratchpad, az, NULL, > + alias_rrset, 0, worker->scratchpad, az, rpz_passthru, > worker->env.views, worker->env.respip_set)) > return 0; > > @@ -772,7 +775,7 @@ answer_from_cache(struct worker* worker, > int* is_secure_answer, struct ub_packed_rrset_key** alias_rrset, > struct reply_info** partial_repp, > struct reply_info* rep, uint16_t id, uint16_t flags, > - struct comm_reply* repinfo, struct edns_data* edns) > + struct comm_reply* repinfo, struct edns_data* edns, int* > rpz_passthru) > { > time_t timenow = *worker->env.now; > uint16_t udpsize = edns->udp_size; > @@ -860,7 +863,7 @@ answer_from_cache(struct worker* worker, > "validation"); > goto bail_out; /* need to validate cache entry first */ > } else if(rep->security == sec_status_secure) { > - if(reply_all_rrsets_secure(rep)) { > + if(reply_an_ns_rrsets_secure(rep)) { > *is_secure_answer = 1; > } else { > if(must_validate) { > @@ -882,7 +885,7 @@ answer_from_cache(struct worker* worker, > if((worker->daemon->use_response_ip || worker->daemon->use_rpz) && > !partial_rep && !apply_respip_action(worker, qinfo, cinfo, > rep, > &repinfo->client_addr, repinfo->client_addrlen, > alias_rrset, > - &encode_rep, worker->env.auth_zones)) { > + &encode_rep, worker->env.auth_zones, rpz_passthru)) { > goto bail_out; > } else if(partial_rep && > !respip_merge_cname(partial_rep, qinfo, rep, cinfo, > @@ -1494,6 +1497,8 @@ worker_handle_request(struct comm_point* > struct reply_info* partial_rep = NULL; > struct query_info* lookup_qinfo = &qinfo; > struct query_info qinfo_tmp; /* placeholder for lookup_qinfo */ > + uint8_t* alias_orig_qname = NULL; /* original qname for logs, if > + a local_alias is used to change the qname. */ > struct respip_client_info* cinfo = NULL, cinfo_tmp; > struct timeval wait_time; > struct check_request_result check_result = {0,0}; > @@ -1511,7 +1516,7 @@ worker_handle_request(struct comm_point* > if (worker->stats.max_query_time_us < wait_queue_time) > worker->stats.max_query_time_us = wait_queue_time; > if(wait_queue_time > > - (long long)(worker->env.cfg->sock_queue_timeout * > 1000000)) { > + (long long)worker->env.cfg->sock_queue_timeout * > 1000000) { > /* count and drop queries that were sitting in the > socket queue too long */ > worker->stats.num_queries_timed_out++; > return 0; > @@ -1936,6 +1941,11 @@ worker_handle_request(struct comm_point* > /* If we've found a local alias, replace the qname with the alias > * target before resolving it. */ > if(qinfo.local_alias) { > + if(qinfo.local_alias->rrset && > + qinfo.local_alias->rrset->rk.dname) > + /* Store the original qname, used for logs, since > + * local_alias can be removed by region_free_all. > */ > + alias_orig_qname = > qinfo.local_alias->rrset->rk.dname; > if(!local_alias_shallow_copy_qname(qinfo.local_alias, > &qinfo.qname, > &qinfo.qname_len)) { > regional_free_all(worker->scratchpad); > @@ -1983,7 +1993,7 @@ lookup_cache: > &alias_rrset, &partial_rep, rep, > *(uint16_t*)(void > *)sldns_buffer_begin(c->buffer), > sldns_buffer_read_u16_at(c->buffer, 2), > repinfo, > - &edns)) { > + &edns, &rpz_passthru)) { > /* prefetch it if the prefetch TTL expired. > * Note that if there is more than one pass > * its qname must be that used for cache > @@ -2101,11 +2111,10 @@ send_reply_rc: > { > struct timeval tv; > memset(&tv, 0, sizeof(tv)); > - if(qinfo.local_alias && qinfo.local_alias->rrset && > - qinfo.local_alias->rrset->rk.dname) { > + if(alias_orig_qname) { > /* log original qname, before the local alias was > * used to resolve that CNAME to something else */ > - qinfo.qname = qinfo.local_alias->rrset->rk.dname; > + qinfo.qname = alias_orig_qname; > log_reply_info(NO_VERBOSE, &qinfo, > &repinfo->client_addr, > repinfo->client_addrlen, > tv, 1, c->buffer, > @@ -2374,6 +2383,8 @@ worker_init(struct worker* worker, struc > worker_stat_timer_cb, worker); > if(!worker->stat_timer) { > log_err("could not create statistics timer"); > + worker_delete(worker); > + return 0; > } > > /* we use the msg_buffer_size as a good estimate for what the > @@ -2526,6 +2537,8 @@ worker_delete(struct worker* worker) > /* don't touch worker->alloc, as it's maintained in daemon */ > regional_destroy(worker->env.scratch); > regional_destroy(worker->scratchpad); > + /* The thread id can reference this worker's id value, so clear > it. */ > + log_thread_set(NULL); > free(worker); > } > > @@ -2534,7 +2547,8 @@ worker_send_query(struct query_info* qin > int want_dnssec, int nocaps, int check_ratelimit, > struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, > size_t zonelen, int tcp_upstream, int ssl_upstream, char* > tls_auth_name, > - struct module_qstate* q, int* was_ratelimited) > + struct module_qstate* q, int* was_ratelimited, > + int* ratelimit_incremented) > { > struct worker* worker = q->env->worker; > struct outbound_entry* e = (struct outbound_entry*)regional_alloc( > @@ -2546,7 +2560,7 @@ worker_send_query(struct query_info* qin > want_dnssec, nocaps, check_ratelimit, tcp_upstream, > ssl_upstream, tls_auth_name, addr, addrlen, zone, zonelen, > q, > worker_handle_service_reply, e, worker->back->udp_buff, > q->env, > - was_ratelimited); > + was_ratelimited, ratelimit_incremented); > if(!e->qsent) { > return NULL; > } > @@ -2595,7 +2609,8 @@ struct outbound_entry* libworker_send_qu > struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t > ATTR_UNUSED(addrlen), > uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), int > ATTR_UNUSED(tcp_upstream), > int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), > - struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited)) > + struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited), > + int* ATTR_UNUSED(ratelimit_incremented)) > { > log_assert(0); > return 0; > Index: dns64/dns64.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/dns64/dns64.c,v > diff -u -p -r1.25 dns64.c > --- dns64/dns64.c 26 May 2026 11:14:11 -0000 1.25 > +++ dns64/dns64.c 20 Sep 2026 09:50:47 -0000 > @@ -643,6 +643,12 @@ handle_event_moddone(struct module_qstat > qstate->return_msg->rep && > reply_find_answer_rrset(&qstate->qinfo, > qstate->return_msg->rep); > int synth_qname = 0; > + if(could_synth && !has_data && qstate->env->need_to_validate && > + qstate->return_msg && qstate->return_msg->rep && > + qstate->return_msg->rep->security == sec_status_bogus) { > + verbose(VERB_ALGO, "dns64: bogus AAAA reply not > synthesized"); > + could_synth = 0; > + } > > if(could_synth && > (!has_data || > @@ -654,8 +660,11 @@ handle_event_moddone(struct module_qstat > > /* Store the response in cache. */ > if( (!iq || !iq->started_no_cache_store) && > + !qstate->rpz_applied && !qstate->rpz_passthru && > + !qstate->is_subnet_answer && > qstate->return_msg && > qstate->return_msg->rep && > + !qstate->fwd_stub_no_cache && > !dns_cache_store( > qstate->env, &qstate->qinfo, > qstate->return_msg->rep, > 0, qstate->prefetch_leeway, 0, NULL, > @@ -717,8 +726,15 @@ dns64_operate(struct module_qstate* qsta > } > if(qstate->ext_state[id] == module_finished) { > iq = (struct dns64_qstate*)qstate->minfo[id]; > - if(iq && iq->state != DNS64_INTERNAL_QUERY) > - qstate->no_cache_store = > iq->started_no_cache_store; > + if(iq && iq->state != DNS64_INTERNAL_QUERY) { > + if(qstate->fwd_stub_no_cache) { > + /* If the forward/stub has no cache, then > + * continue with the query with no cache. > */ > + qstate->no_cache_store = > qstate->fwd_stub_no_cache; > + } else { > + qstate->no_cache_store = > iq->started_no_cache_store; > + } > + } > } > } > > @@ -825,6 +841,7 @@ dns64_adjust_a(int id, struct module_qst > size_t i, s; > struct packed_rrset_data* fd, *dd; > struct ub_packed_rrset_key* fk, *dk; > + int allocated_return_msg = 0; > > verbose(VERB_ALGO, "converting A answers to AAAA answers"); > > @@ -840,6 +857,7 @@ dns64_adjust_a(int id, struct module_qst > return; > memset(super->return_msg, 0, sizeof(*super->return_msg)); > super->return_msg->qinfo = super->qinfo; > + allocated_return_msg = 1; > } > > rep = qstate->return_msg->rep; > @@ -852,11 +870,14 @@ dns64_adjust_a(int id, struct module_qst > rep->serve_expired_norec_ttl, > rep->an_numrrsets, rep->ns_numrrsets, rep->ar_numrrsets, > rep->rrset_count, rep->security, LDNS_EDE_NONE); > - if(!cp) > + if(!cp) { > + if(allocated_return_msg) super->return_msg = NULL; > return; > + } > > /* allocate ub_key structures special or not */ > if(!reply_info_alloc_rrset_keys(cp, NULL, super->region)) { > + if(allocated_return_msg) super->return_msg = NULL; > return; > } > > @@ -871,8 +892,10 @@ dns64_adjust_a(int id, struct module_qst > if(ian_numrrsets && fk->rk.type == > htons(LDNS_RR_TYPE_A)) { > /* also sets dk->entry.hash */ > dns64_synth_aaaa_data(fk, fd, dk, &dd, > super->region, dns64_env); > - if(!dd) > + if(!dd) { > + if(allocated_return_msg) super->return_msg > = NULL; > return; > + } > /* Delete negative AAAA record from cache stored by > * the iterator module */ > rrset_cache_remove(super->env->rrset_cache, > dk->rk.dname, > @@ -889,15 +912,19 @@ dns64_adjust_a(int id, struct module_qst > dk->rk.dname = > (uint8_t*)regional_alloc_init(super->region, > fk->rk.dname, fk->rk.dname_len); > > - if(!dk->rk.dname) > + if(!dk->rk.dname) { > + if(allocated_return_msg) super->return_msg > = NULL; > return; > + } > > s = packed_rrset_sizeof(fd); > dd = (struct > packed_rrset_data*)regional_alloc_init( > super->region, fd, s); > > - if(!dd) > + if(!dd) { > + if(allocated_return_msg) super->return_msg > = NULL; > return; > + } > } > > packed_rrset_ptr_fixup(dd); > @@ -928,8 +955,10 @@ dns64_adjust_ptr(struct module_qstate* q > return; > super->return_msg->qinfo = super->qinfo; > if (!(super->return_msg->rep = > reply_info_copy(qstate->return_msg->rep, > - NULL, super->region))) > + NULL, super->region))) { > + super->return_msg = NULL; > return; > + } > > /* > * Adjust the domain name of the answer RR set so that it matches the > @@ -998,6 +1027,21 @@ dns64_inform_super(struct module_qstate* > /* Use return code from A query in response to client. */ > if (super->return_rcode != LDNS_RCODE_NOERROR) > super->return_rcode = qstate->return_rcode; > + /* RPZ applied to the subquery need to then change (not cache) > + * the super query. With the super query not cached, it is > + * going to run the state machine modules on incoming queries, > + * that fetch the subquery (cache) response, and modify it > + * according to the rpz policy. That makes the synthesized > + * super query also adjusted by rpz policies. But loses cache > + * hits. Even though the subquery likely is answered from cache, > + * internally in its state machine process. */ > + if(qstate->rpz_applied) > + super->rpz_applied = 1; > + if(qstate->rpz_passthru) > + super->rpz_passthru = 1; > + > + /* Since the super qstate has a new response, its errinf is > removed. */ > + super->errinf = NULL; > > /* Generate a response suitable for the original query. */ > if (qstate->qinfo.qtype == LDNS_RR_TYPE_A) { > @@ -1006,9 +1050,16 @@ dns64_inform_super(struct module_qstate* > log_assert(qstate->qinfo.qtype == LDNS_RR_TYPE_PTR); > dns64_adjust_ptr(qstate, super); > } > + /* If the sub-query has no cache store, then also the super query. > */ > + if(qstate->fwd_stub_no_cache) > + super->fwd_stub_no_cache = 1; > > /* Store the generated response in cache. */ > - if ( (!super_dq || !super_dq->started_no_cache_store) && > + if ( super->return_msg && super->return_msg->rep && > + (!super_dq || !super_dq->started_no_cache_store) && > + !qstate->fwd_stub_no_cache && > + !super->rpz_applied && !super->rpz_passthru && > + !super->is_subnet_answer && > !dns_cache_store(super->env, &super->qinfo, > super->return_msg->rep, > 0, super->prefetch_leeway, 0, NULL, super->query_flags, > qstate->qstarttime, qstate->is_valrec)) > Index: dnscrypt/dnscrypt.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/dnscrypt/dnscrypt.c,v > diff -u -p -r1.10 dnscrypt.c > --- dnscrypt/dnscrypt.c 27 Jul 2026 14:14:39 -0000 1.10 > +++ dnscrypt/dnscrypt.c 20 Sep 2026 09:50:47 -0000 > @@ -842,7 +842,14 @@ dnsc_parse_keys(struct dnsc_env *env, st > if(memcmp(current_keypair->crypt_publickey, > env->signed_certs[c].server_publickey, > crypto_box_PUBLICKEYBYTES) == 0) { > - dnsccert *current_cert = > &env->certs[cert_id++]; > + dnsccert* current_cert; > + if(cert_id >= env->signed_certs_count) { > + log_err("dnscrypt: secret key %s > matches a cert that " > + "is already bound to > another key (duplicate " > + "dnscrypt-secret-key?)", > head->str); > + return -1; > + } > + current_cert = &env->certs[cert_id++]; > found_cert = 1; > current_cert->keypair = current_keypair; > memcpy(current_cert->magic_query, > Index: dnstap/dnstap.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/dnstap/dnstap.c,v > diff -u -p -r1.14 dnstap.c > --- dnstap/dnstap.c 26 Sep 2025 07:32:37 -0000 1.14 > +++ dnstap/dnstap.c 20 Sep 2026 09:50:47 -0000 > @@ -176,26 +176,29 @@ dt_create(struct config_file* cfg) > env->dtio = dt_io_thread_create(); > if(!env->dtio) { > log_err("malloc failure"); > - free(env); > + dt_delete(env); > return NULL; > } > if(!dt_io_thread_apply_cfg(env->dtio, cfg)) { > - dt_io_thread_delete(env->dtio); > - free(env); > + dt_delete(env); > + return NULL; > + } > + if(!dt_apply_cfg(env, cfg)) { > + dt_delete(env); > return NULL; > } > - dt_apply_cfg(env, cfg); > return env; > } > > -static void > +static int > dt_apply_identity(struct dt_env *env, struct config_file *cfg) > { > char buf[MAXHOSTNAMELEN+1]; > if (!cfg->dnstap_send_identity) { > free(env->identity); > env->identity = NULL; > - return; > + env->len_identity = 0; > + return 1; > } > free(env->identity); > if (cfg->dnstap_identity == NULL || cfg->dnstap_identity[0] == 0) { > @@ -203,36 +206,49 @@ dt_apply_identity(struct dt_env *env, st > buf[MAXHOSTNAMELEN] = 0; > env->identity = strdup(buf); > } else { > - fatal_exit("dt_apply_identity: gethostname() > failed"); > + log_err("dt_apply_identity: gethostname() failed: > %s", > + strerror(errno)); > + env->identity = NULL; > + env->len_identity = 0; > + return 0; > } > } else { > env->identity = strdup(cfg->dnstap_identity); > } > - if (env->identity == NULL) > - fatal_exit("dt_apply_identity: strdup() failed"); > + if (env->identity == NULL) { > + log_err("dt_apply_identity: strdup() failed"); > + env->len_identity = 0; > + return 0; > + } > env->len_identity = (unsigned int)strlen(env->identity); > verbose(VERB_OPS, "dnstap identity field set to \"%s\"", > env->identity); > + return 1; > } > > -static void > +static int > dt_apply_version(struct dt_env *env, struct config_file *cfg) > { > if (!cfg->dnstap_send_version) { > free(env->version); > env->version = NULL; > - return; > + env->len_version = 0; > + return 1; > } > free(env->version); > if (cfg->dnstap_version == NULL || cfg->dnstap_version[0] == 0) > env->version = strdup(PACKAGE_STRING); > else > env->version = strdup(cfg->dnstap_version); > - if (env->version == NULL) > - fatal_exit("dt_apply_version: strdup() failed"); > + if (env->version == NULL) { > + log_err("dt_apply_version: strdup() failed"); > + env->len_version = 0; > + return 0; > + } > env->len_version = (unsigned int)strlen(env->version); > verbose(VERB_OPS, "dnstap version field set to \"%s\"", > env->version); > + return 1; > } > > void > @@ -276,15 +292,18 @@ dt_apply_logcfg(struct dt_env *env, stru > lock_basic_unlock(&env->sample_lock); > } > > -void > +int > dt_apply_cfg(struct dt_env *env, struct config_file *cfg) > { > if (!cfg->dnstap) > - return; > + return 1; > > - dt_apply_identity(env, cfg); > - dt_apply_version(env, cfg); > dt_apply_logcfg(env, cfg); > + if(!dt_apply_identity(env, cfg)) > + return 0; > + if(!dt_apply_version(env, cfg)) > + return 0; > + return 1; > } > > int > Index: dnstap/dnstap.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/dnstap/dnstap.h,v > diff -u -p -r1.1.1.8 dnstap.h > --- dnstap/dnstap.h 31 Aug 2025 21:36:34 -0000 1.1.1.8 > +++ dnstap/dnstap.h 20 Sep 2026 09:50:47 -0000 > @@ -102,9 +102,9 @@ dt_create(struct config_file* cfg); > * Apply config settings. > * @param env: dnstap environment object. > * @param cfg: new config settings. > + * @return false on failure. > */ > -void > -dt_apply_cfg(struct dt_env *env, struct config_file *cfg); > +int dt_apply_cfg(struct dt_env *env, struct config_file *cfg); > > /** > * Apply config settings for log enable for message types. > Index: dnstap/dtstream.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/dnstap/dtstream.c,v > diff -u -p -r1.4 dtstream.c > --- dnstap/dtstream.c 26 May 2026 11:14:11 -0000 1.4 > +++ dnstap/dtstream.c 20 Sep 2026 09:50:47 -0000 > @@ -222,7 +222,7 @@ dt_msg_queue_start_timer(struct dt_msg_q > tv.tv_usec = 0; > /* If it is already set, keep it running. */ > if(!comm_timer_is_set(mq->wakeup_timer)) > - comm_timer_set(mq->wakeup_timer, &tv); > + comm_timer_set(mq->wakeup_timer, &tv); > } else { > tv.tv_sec = 0; > tv.tv_usec = 0; > @@ -1554,7 +1554,7 @@ void dtio_output_cb(int ATTR_UNUSED(fd), > } > } > if(!dtio->cur_msg) > - return; /* nothing to do */ > + return; /* nothing to do */ > } > } > > @@ -2144,7 +2144,7 @@ static void* dnstap_io(void* arg) > #endif > log_thread_set(&dtio->threadnum); > > - ub_thread_setname(dtio->tid, name); > + ub_thread_setname(ub_thread_self(), name); > > /* setup */ > verbose(VERB_ALGO, "start dnstap io thread"); > Index: dnstap/unbound-dnstap-socket.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/dnstap/unbound-dnstap-socket.c,v > diff -u -p -r1.6 unbound-dnstap-socket.c > --- dnstap/unbound-dnstap-socket.c 27 Jul 2026 14:14:39 -0000 1.6 > +++ dnstap/unbound-dnstap-socket.c 20 Sep 2026 09:50:47 -0000 > @@ -75,18 +75,18 @@ > static void usage(char* argv[]) > { > printf("usage: %s [options]\n", argv[0]); > - printf(" Listen to dnstap messages\n"); > + printf(" Listen to dnstap messages\n"); > printf("stdout has dnstap log, stderr has verbose server log\n"); > - printf("-u listen to unix socket with this file > name\n"); > - printf("-s listen for TCP on the IP and port\n"); > - printf("-t listen for TLS on IP and port\n"); > - printf("-x server key file for TLS service\n"); > - printf("-y server cert file for TLS service\n"); > - printf("-z cert file to verify client connections\n"); > - printf("-l long format for DNS printout\n"); > - printf("-v more verbose log output\n"); > + printf("-u listen to unix socket with this > file name\n"); > + printf("-s listen for TCP on the IP and > port\n"); > + printf("-t listen for TLS on IP and port\n"); > + printf("-x server key file for TLS > service\n"); > + printf("-y server cert file for TLS > service\n"); > + printf("-z cert file to verify client > connections\n"); > + printf("-l long format for DNS printout\n"); > + printf("-v more verbose log output\n"); > printf("-c internal unit test and exit\n"); > - printf("-h this help text\n"); > + printf("-h this help text\n"); > exit(1); > } > > @@ -1659,7 +1659,8 @@ struct outbound_entry* worker_send_query > socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), > size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), > int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), > - struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited)) > + struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited), > + int* ATTR_UNUSED(ratelimit_incremented)) > { > log_assert(0); > return 0; > @@ -1693,7 +1694,8 @@ struct outbound_entry* libworker_send_qu > socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), > size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), > int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), > - struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited)) > + struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited), > + int* ATTR_UNUSED(ratelimit_incremented)) > { > log_assert(0); > return 0; > Index: doc/README > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/README,v > diff -u -p -r1.47 README > --- doc/README 27 Jul 2026 14:14:39 -0000 1.47 > +++ doc/README 20 Sep 2026 09:50:48 -0000 > @@ -1,4 +1,4 @@ > -README for Unbound 1.25.2 > +README for Unbound 1.26.1 > Copyright 2007 NLnet Labs > http://unbound.net > > Index: doc/example.conf.in > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/example.conf.in,v > diff -u -p -r1.52 example.conf.in > --- doc/example.conf.in 27 Jul 2026 14:14:39 -0000 1.52 > +++ doc/example.conf.in 20 Sep 2026 09:50:48 -0000 > @@ -1,7 +1,7 @@ > # > # Example configuration file. > # > -# See unbound.conf(5) man page, version 1.25.2. > +# See unbound.conf(5) man page, version 1.26.1. > # > # this is a comment. > > @@ -203,6 +203,12 @@ server: > # protects against poison attempts. > # iter-scrub-promiscuous: yes > > + # Limit on number of DNSSEC validation attempts for a query. > + # val-validation-attempts: 32 > + > + # Limit on number of DNSSEC hash attempts for a query. > + # val-hash-attempts: 32 > + > # msec for waiting for an unknown server to reply. Increase if you > # are behind a slow satellite link, to eg. 1128. > # unknown-server-time-limit: 376 > @@ -728,7 +734,7 @@ server: > # non-secure data. Useful to shield the users of this validator > from > # potential bogus data in the additional section. All unsigned data > # in the additional section is removed from secure messages. > - # val-clean-additional: yes > + # val-clean-additional: no > > # Turn permissive mode on to permit bogus messages. Thus, messages > # for which security checks failed will be returned to clients, > @@ -899,6 +905,10 @@ 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 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. > # > @@ -1287,6 +1297,9 @@ remote-control: > # zonemd-check: no > # zonemd-reject-absence: no > # zonefile: "example.org.zone" > +# max-transfer-size: 0 > +# max-transfer-time: 0 > + > > # Views > # Create named views. Name must be unique. > @@ -1453,3 +1466,5 @@ remote-control: > # rpz-signal-nxdomain-ra: no > # for-downstream: no > # tags: "example" > +# max-transfer-size: 0 > +# max-transfer-time: 0 > Index: doc/libunbound.3.in > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/libunbound.3.in,v > diff -u -p -r1.50 libunbound.3.in > --- doc/libunbound.3.in 27 Jul 2026 14:14:39 -0000 1.50 > +++ doc/libunbound.3.in 20 Sep 2026 09:50:48 -0000 > @@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2 > .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] > .in \\n[rst2man-indent\\n[rst2man-indent-level]]u > .. > -.TH "LIBUNBOUND" "3" "Jul 22, 2026" "1.25.2" "Unbound" > +.TH "LIBUNBOUND" "3" "Sep 16, 2026" "1.26.1" "Unbound" > .SH NAME > -libunbound \- Unbound DNS validating resolver 1.25.2 functions. > +libunbound \- Unbound DNS validating resolver 1.26.1 functions. > .SH SYNOPSIS > .sp > \fB#include \fP > Index: doc/unbound-anchor.8.in > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-anchor.8.in,v > diff -u -p -r1.49 unbound-anchor.8.in > --- doc/unbound-anchor.8.in 27 Jul 2026 14:14:39 -0000 1.49 > +++ doc/unbound-anchor.8.in 20 Sep 2026 09:50:48 -0000 > @@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2 > .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] > .in \\n[rst2man-indent\\n[rst2man-indent-level]]u > .. > -.TH "UNBOUND-ANCHOR" "8" "Jul 22, 2026" "1.25.2" "Unbound" > +.TH "UNBOUND-ANCHOR" "8" "Sep 16, 2026" "1.26.1" "Unbound" > .SH NAME > -unbound-anchor \- Unbound 1.25.2 anchor utility. > +unbound-anchor \- Unbound 1.26.1 anchor utility. > .SH SYNOPSIS > .sp > \fBunbound\-anchor\fP [\fBopts\fP] > Index: doc/unbound-checkconf.8.in > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-checkconf.8.in,v > diff -u -p -r1.49 unbound-checkconf.8.in > --- doc/unbound-checkconf.8.in 27 Jul 2026 14:14:39 -0000 1.49 > +++ doc/unbound-checkconf.8.in 20 Sep 2026 09:50:48 -0000 > @@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2 > .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] > .in \\n[rst2man-indent\\n[rst2man-indent-level]]u > .. > -.TH "UNBOUND-CHECKCONF" "8" "Jul 22, 2026" "1.25.2" "Unbound" > +.TH "UNBOUND-CHECKCONF" "8" "Sep 16, 2026" "1.26.1" "Unbound" > .SH NAME > -unbound-checkconf \- Check Unbound 1.25.2 configuration file for errors. > +unbound-checkconf \- Check Unbound 1.26.1 configuration file for errors. > .SH SYNOPSIS > .sp > \fBunbound\-checkconf\fP [\fB\-hf\fP] [\fB\-o option\fP] [cfgfile] > Index: doc/unbound-control.8.in > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-control.8.in,v > diff -u -p -r1.51 unbound-control.8.in > --- doc/unbound-control.8.in 27 Jul 2026 14:14:39 -0000 1.51 > +++ doc/unbound-control.8.in 20 Sep 2026 09:50:48 -0000 > @@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2 > .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] > .in \\n[rst2man-indent\\n[rst2man-indent-level]]u > .. > -.TH "UNBOUND-CONTROL" "8" "Jul 22, 2026" "1.25.2" "Unbound" > +.TH "UNBOUND-CONTROL" "8" "Sep 16, 2026" "1.26.1" "Unbound" > .SH NAME > -unbound-control \- Unbound 1.25.2 remote server control utility. > +unbound-control \- Unbound 1.26.1 remote server control utility. > .SH SYNOPSIS > .sp > \fBunbound\-control\fP [\fB\-hq\fP] [\fB\-c cfgfile\fP] [\fB\-s > server\fP] command > @@ -354,6 +354,8 @@ If the name already has no items, nothin > 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 > Index: doc/unbound-control.rst > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-control.rst,v > diff -u -p -r1.1.1.3 unbound-control.rst > --- doc/unbound-control.rst 26 May 2026 11:10:50 -0000 1.1.1.3 > +++ doc/unbound-control.rst 20 Sep 2026 09:50:48 -0000 > @@ -347,6 +347,8 @@ There are several commands that the serv > 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. > > > @@UAHL@unbound-control.commands@local_zones@@ > Index: doc/unbound-host.1.in > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-host.1.in,v > diff -u -p -r1.51 unbound-host.1.in > --- doc/unbound-host.1.in 27 Jul 2026 14:14:39 -0000 1.51 > +++ doc/unbound-host.1.in 20 Sep 2026 09:50:48 -0000 > @@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2 > .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] > .in \\n[rst2man-indent\\n[rst2man-indent-level]]u > .. > -.TH "UNBOUND-HOST" "1" "Jul 22, 2026" "1.25.2" "Unbound" > +.TH "UNBOUND-HOST" "1" "Sep 16, 2026" "1.26.1" "Unbound" > .SH NAME > -unbound-host \- Unbound 1.25.2 DNS lookup utility. > +unbound-host \- Unbound 1.26.1 DNS lookup utility. > .SH SYNOPSIS > .sp > \fBunbound\-host\fP [\fB\-C configfile\fP] [\fB\-vdhr46D\fP] [\fB\-c > class\fP] > Index: doc/unbound.8.in > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/unbound.8.in,v > diff -u -p -r1.52 unbound.8.in > --- doc/unbound.8.in 27 Jul 2026 14:14:39 -0000 1.52 > +++ doc/unbound.8.in 20 Sep 2026 09:50:48 -0000 > @@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2 > .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] > .in \\n[rst2man-indent\\n[rst2man-indent-level]]u > .. > -.TH "UNBOUND" "8" "Jul 22, 2026" "1.25.2" "Unbound" > +.TH "UNBOUND" "8" "Sep 16, 2026" "1.26.1" "Unbound" > .SH NAME > -unbound \- Unbound DNS validating resolver 1.25.2. > +unbound \- Unbound DNS validating resolver 1.26.1. > .SH SYNOPSIS > .sp > \fBunbound\fP [\fB\-hdpVv\fP] [\fB\-c \fP] > Index: doc/unbound.conf.5.in > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/unbound.conf.5.in,v > diff -u -p -r1.57 unbound.conf.5.in > --- doc/unbound.conf.5.in 27 Jul 2026 14:14:39 -0000 1.57 > +++ doc/unbound.conf.5.in 20 Sep 2026 09:50:48 -0000 > @@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2 > .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] > .in \\n[rst2man-indent\\n[rst2man-indent-level]]u > .. > -.TH "UNBOUND.CONF" "5" "Jul 22, 2026" "1.25.2" "Unbound" > +.TH "UNBOUND.CONF" "5" "Sep 16, 2026" "1.26.1" "Unbound" > .SH NAME > -unbound.conf \- Unbound 1.25.2 configuration file. > +unbound.conf \- Unbound 1.26.1 configuration file. > .SH SYNOPSIS > .sp > \fBunbound.conf\fP > @@ -636,7 +636,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 > @@ -2230,6 +2230,13 @@ The defensive action is to clear the rrs > 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 > @@ -2280,6 +2287,8 @@ If yes, deny queries of type ANY with an > 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 > @@ -2828,6 +2837,9 @@ The types are > \fI\%inform_redirect\fP, > \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, > @@ -3018,6 +3030,32 @@ use IPv6 protocol and avoid any queries > .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 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. > @@ -3485,6 +3523,18 @@ For example, 1000 may be a suitable valu > 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 > @@ -4512,6 +4562,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. > @@ -5724,6 +5800,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 > @@ -5845,6 +5925,32 @@ Enclose list of tags in quotes (\fB\(dq\ > .sp > 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 > Index: doc/unbound.conf.rst > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/doc/unbound.conf.rst,v > diff -u -p -r1.6 unbound.conf.rst > --- doc/unbound.conf.rst 27 Jul 2026 14:14:39 -0000 1.6 > +++ doc/unbound.conf.rst 20 Sep 2026 09:50:48 -0000 > @@ -642,7 +642,7 @@ These options are part of the ``server:` > > @@UAHL@unbound.conf@so-sndbuf@@: ** > 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: > > .. code-block:: text > @@ -2107,6 +2107,8 @@ These options are part of the ``server:` > 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. > > Default: no > > @@ -2318,7 +2320,7 @@ These options are part of the ``server:` > Use this setting to protect the users that rely on this validator for > authentication from potentially bad data in the additional section. > > - Default: yes > + Default: no > > > @@UAHL@unbound.conf@val-log-level@@: ** > @@ -2590,6 +2592,9 @@ These options are part of the ``server:` > :ref:`inform_redirect`, > > :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`, > @@ -2739,6 +2744,26 @@ These options are part of the ``server:` > 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@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. > @@ -3085,6 +3110,18 @@ These options are part of the ``server:` > overloaded with random names, and keeps unbound from sending traffic > to the > nameservers for those zones. > > + 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 :ref:`ratelimit-factor`, 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. > + > .. note:: Configured forwarders are excluded from ratelimiting. > > Default: 0 > @@ -3321,6 +3358,26 @@ These options are part of the ``server:` > Default: yes > > > +@@UAHL@unbound.conf@val-validation-attempts@@: ** > + Limit on the number of DNSSEC validation attempts for a query. > + This protects against too large numbers of cryptographic operations, > + like for a deep delegation chain. > + This counts attempts to validate RRSIGs. > + When it is exceeded, the query fails. > + > + Default: 32 > + > + > +@@UAHL@unbound.conf@val-hash-attempts@@: ** > + Limit on the number of DNSSEC hash attempts for a query. > + This protects against too large numbers of cryptographic operations, > + like for a deep delegation chain. > + This counts DS hash attempts to match DNSKEYs. > + When it is exceeded, the query fails. > + > + Default: 32 > + > + > @@UAHL@unbound.conf@fast-server-permil@@: ** > Specify how many times out of 1000 to pick from the set of fastest > servers. > 0 turns the feature off. > @@ -4018,6 +4075,31 @@ fallback activates to fetch from the ups > If the file does not exist or is empty, Unbound will attempt to fetch > zone > data (eg. from the primary servers). > > + > +@@UAHL@unbound.conf.auth@max-transfer-size@@: ** > + 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 'k', 'm' or 'g' for kilobytes, > megabytes > + or gigabytes (1024*1024 bytes in a megabyte). > + The value ``0`` disables the feature. > + > + Only consider for untrusted/misbehaving primaries that could hog > resources > + and bring down the resolver. > + > + Default: 0 > + > + > +@@UAHL@unbound.conf.auth@max-transfer-time@@: ** > + Maximum time in milliseconds that a zone transfer is allowed to take > from > + the start. > + The value ``0`` disables the feature. > + > + Only consider for untrusted/misbehaving primaries that could hog > resources > + and bring down the resolver. > + > + Default: 0 > + > + > .. _unbound.conf.view: > > View Options > @@ -5098,6 +5180,10 @@ answer queries with that content. > 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. > > + 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. > + > > @@UAHL@unbound.conf.rpz@master@@: ** > Alternate syntax for :ref:`primary`. > @@ -5197,6 +5283,31 @@ answer queries with that content. > > If no tags are specified the policies from this section will be > applied for > all clients. > + > + > +@@UAHL@unbound.conf.rpz@max-transfer-size@@: ** > + 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 'k', 'm' or 'g' for kilobytes, > megabytes > + or gigabytes (1024*1024 bytes in a megabyte). > + The value ``0`` disables the feature. > + > + Only consider for untrusted/misbehaving primaries that could hog > resources > + and bring down the resolver. > + > + Default: 0 > + > + > +@@UAHL@unbound.conf.rpz@max-transfer-time@@: ** > + Maximum time in milliseconds that a zone transfer is allowed to take > from > + the start. > + The value ``0`` disables the feature. > + > + Only consider for untrusted/misbehaving primaries that could hog > resources > + and bring down the resolver. > + > + Default: 0 > + > > Memory Control Example > ---------------------- > Index: edns-subnet/addrtree.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/edns-subnet/addrtree.c,v > diff -u -p -r1.4 addrtree.c > --- edns-subnet/addrtree.c 20 Oct 2022 08:26:14 -0000 1.4 > +++ edns-subnet/addrtree.c 20 Sep 2026 09:50:48 -0000 > @@ -459,6 +459,7 @@ addrtree_insert(struct addrtree *tree, c > /* Data is stored in other leafnode */ > node = newnode; > newnode = node_create(tree, elem, scope, ttl); > + if (!newnode) return; > if (!edge_create(newnode, addr, sourcemask, node, > index^1)) { > clean_node(tree, newnode); > Index: edns-subnet/subnetmod.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/edns-subnet/subnetmod.c,v > diff -u -p -r1.20 subnetmod.c > --- edns-subnet/subnetmod.c 26 May 2026 11:14:11 -0000 1.20 > +++ edns-subnet/subnetmod.c 20 Sep 2026 09:50:48 -0000 > @@ -1015,6 +1015,7 @@ subnetmod_operate(struct module_qstate * > subnet_ecs_opt_list_append(&sq->ecs_client_out, > &qstate->edns_opts_front_out, qstate, > qstate->region); > + qstate->is_subnet_answer = 1; > } > sq->wait_subquery_done = 0; > qstate->ext_state[id] = module_finished; > @@ -1094,6 +1095,7 @@ subnetmod_operate(struct module_qstate * > qstate->env->cfg->prefetch)) { > sne->num_msg_cache++; > lock_rw_unlock(&sne->biglock); > + qstate->is_subnet_answer = 1; > verbose(VERB_QUERY, "subnetcache: answered > from cache"); > qstate->ext_state[id] = module_finished; > > @@ -1165,6 +1167,7 @@ subnetmod_operate(struct module_qstate * > subnet_ecs_opt_list_append(&sq->ecs_client_out, > &qstate->edns_opts_front_out, qstate, > qstate->region); > + qstate->is_subnet_answer = 1; > if(verbosity >= VERB_ALGO) { > subnet_log_print("reply has edns subnet", > edns_opt_list_find( > Index: ipsecmod/ipsecmod-whitelist.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/ipsecmod/ipsecmod-whitelist.c,v > diff -u -p -r1.1 ipsecmod-whitelist.c > --- ipsecmod/ipsecmod-whitelist.c 12 Aug 2017 11:22:46 -0000 1.1 > +++ ipsecmod/ipsecmod-whitelist.c 20 Sep 2026 09:50:48 -0000 > @@ -100,6 +100,8 @@ ipsecmod_whitelist_apply_cfg(struct ipse > 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); > Index: ipsecmod/ipsecmod.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/ipsecmod/ipsecmod.c,v > diff -u -p -r1.9 ipsecmod.c > --- ipsecmod/ipsecmod.c 26 May 2026 11:14:11 -0000 1.9 > +++ ipsecmod/ipsecmod.c 20 Sep 2026 09:50:48 -0000 > @@ -51,6 +51,9 @@ > #include "util/config_file.h" > #include "services/cache/dns.h" > #include "sldns/wire2str.h" > +#ifdef HAVE_SYS_WAIT_H > +#include > +#endif > > /** Apply configuration to ipsecmod module 'global' state. */ > static int > @@ -60,6 +63,11 @@ ipsecmod_apply_cfg(struct ipsecmod_env* > log_err("ipsecmod: missing ipsecmod-hook."); > return 0; > } > + if(access(cfg->ipsecmod_hook, X_OK) != 0) { > + log_err("ipsecmod: ipsecmod-hook '%s' is not an executable > file: %s", > + cfg->ipsecmod_hook, strerror(errno)); > + return 0; > + } > if(cfg->ipsecmod_whitelist && > !ipsecmod_whitelist_apply_cfg(ipsecmod_env, cfg)) > return 0; > @@ -250,27 +258,16 @@ call_hook(struct module_qstate* qstate, > struct ipsecmod_env* ATTR_UNUSED(ie)) > { > size_t slen, tempdata_len, tempstring_len, i; > - char str[65535], *s, *tempstring; > + char qname_s[LDNS_MAX_DOMAINLEN*5+16], ttl_s[32], a_s[32768], > k_s[32768]; > + char *s, *tempstring; > int w = 0, w_temp, qtype; > struct ub_packed_rrset_key* rrset_key; > struct packed_rrset_data* rrset_data; > uint8_t *tempdata; > + pid_t pid; > + int st; > + char* argv[6]; > > - /* Check if a shell is available */ > - if(system(NULL) == 0) { > - log_err("ipsecmod: no shell available for ipsecmod-hook"); > - return 0; > - } > - > - /* Zero the buffer. */ > - s = str; > - slen = sizeof(str); > - memset(s, 0, slen); > - > - /* Copy the hook into the buffer. */ > - w += sldns_str_print(&s, &slen, "%s", > qstate->env->cfg->ipsecmod_hook); > - /* Put space into the buffer. */ > - w += sldns_str_print(&s, &slen, " "); > /* Copy the qname into the buffer. */ > tempstring = sldns_wire2str_dname(qstate->qinfo.qname, > qstate->qinfo.qname_len); > @@ -283,17 +280,24 @@ call_hook(struct module_qstate* qstate, > free(tempstring); > return 0; > } > - w += sldns_str_print(&s, &slen, "\"%s\"", tempstring); > + if(strlen(tempstring)+1 > sizeof(qname_s)) { > + log_err("ipsecmod: string too long"); > + free(tempstring); > + return 0; > + } > + snprintf(qname_s, sizeof(qname_s), "%s", tempstring); > free(tempstring); > - /* Put space into the buffer. */ > - w += sldns_str_print(&s, &slen, " "); > + > /* Copy the IPSECKEY TTL into the buffer. */ > rrset_data = (struct > packed_rrset_data*)iq->ipseckey_rrset->entry.data; > - w += sldns_str_print(&s, &slen, "\"%ld\"", (long)rrset_data->ttl); > - /* Put space into the buffer. */ > - w += sldns_str_print(&s, &slen, " "); > + snprintf(ttl_s, sizeof(ttl_s), "%ld", (long)rrset_data->ttl); > + > 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 > @@ -304,9 +308,15 @@ call_hook(struct module_qstate* qstate, > return 0; > } > rrset_data = (struct packed_rrset_data*)rrset_key->entry.data; > - /* Copy the A/AAAA record(s) into the buffer. Start and end this > section > - * with a double quote. */ > - w += sldns_str_print(&s, &slen, "\""); > + if(!rrset_data) { > + log_err("ipsecmod: Answer has no data"); > + return 0; > + } > + /* Copy the A/AAAA record(s) into the buffer. */ > + w = 0; > + s = a_s; > + slen = sizeof(a_s); > + memset(s, 0, slen); > for(i=0; icount; i++) { > if(i > 0) { > /* Put space into the buffer. */ > @@ -322,7 +332,7 @@ call_hook(struct module_qstate* qstate, > } else if((size_t)w_temp >= slen) { > s = NULL; /* We do not want str to point outside > of buffer. */ > slen = 0; > - log_err("ipsecmod: shell command too long"); > + log_err("ipsecmod: command addr argument too > long"); > return 0; > } else { > s += w_temp; > @@ -330,12 +340,17 @@ call_hook(struct module_qstate* qstate, > w += w_temp; > } > } > - w += sldns_str_print(&s, &slen, "\""); > - /* Put space into the buffer. */ > - w += sldns_str_print(&s, &slen, " "); > + if(w >= (int)sizeof(a_s)) { > + log_err("ipsecmod: command addr argument too long"); > + return 0; > + } > + > /* Copy the IPSECKEY record(s) into the buffer. Start and end this > section > * with a double quote. */ > - w += sldns_str_print(&s, &slen, "\""); > + w = 0; > + s = k_s; > + slen = sizeof(k_s); > + memset(s, 0, slen); > rrset_data = (struct > packed_rrset_data*)iq->ipseckey_rrset->entry.data; > for(i=0; icount; i++) { > if(i > 0) { > @@ -362,15 +377,44 @@ call_hook(struct module_qstate* qstate, > w += w_temp; > } > } > - w += sldns_str_print(&s, &slen, "\""); > - if(w >= (int)sizeof(str)) { > - log_err("ipsecmod: shell command too long"); > + if(w >= (int)sizeof(k_s)) { > + log_err("ipsecmod: command ipseckey argument too long"); > return 0; > } > - verbose(VERB_ALGO, "ipsecmod: shell command: '%s'", str); > + > /* ipsecmod-hook should return 0 on success. */ > - if(system(str) != 0) > + /* exec the ipsecmod-hook */ > + argv[0] = qstate->env->cfg->ipsecmod_hook; > + argv[1] = qname_s; > + argv[2] = ttl_s; > + argv[3] = a_s; > + argv[4] = k_s; > + argv[5] = NULL; > + verbose(VERB_ALGO, "ipsecmod: exec %s \"%s\" %s \"%s\" \"%s\"", > + argv[0], argv[1], argv[2], argv[3], argv[4]); > + if((pid = fork()) < 0) { > + log_err("ipsecmod: for exec, can not fork: %s", > + strerror(errno)); > + return 0; > + } > + if(pid == 0) { > + if(execv(argv[0], argv) < 0) > + fprintf(stderr, "ipsecmod: execv: %s\n", > + strerror(errno)); > + _exit(127); > + } > + while(1) { > + if(waitpid(pid, &st, 0) < 0) { > + if(errno == EINTR) > + continue; > + log_err("ipsecmod: wait_pid: %s", strerror(errno)); > + } > + break; > + } > + if(!(WIFEXITED(st) && WEXITSTATUS(st) == 0)) { > + /* the command failed */ > return 0; > + } > return 1; > } > > @@ -435,6 +479,12 @@ ipsecmod_handle_query(struct module_qsta > * 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. */ > Index: ipset/ipset.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/ipset/ipset.c,v > diff -u -p -r1.2 ipset.c > --- ipset/ipset.c 4 Sep 2024 09:36:40 -0000 1.2 > +++ ipset/ipset.c 20 Sep 2026 09:50:48 -0000 > @@ -129,7 +129,7 @@ static int add_to_ipset(filter_dev dev, > default: > errno = EAFNOSUPPORT; > return -1; > -} > + } > addr.pfra_af = af; > > if (ioctl(dev, DIOCRADDADDRS, &io) == -1) { > @@ -143,7 +143,7 @@ static int add_to_ipset(filter_dev dev, > struct nlmsghdr *nlh; > struct nfgenmsg *nfg; > struct nlattr *nested[2]; > - static char buffer[BUFF_LEN]; > + char buffer[BUFF_LEN]; > > if (strlen(setname) >= IPSET_MAXNAMELEN) { > errno = ENAMETOOLONG; > @@ -208,13 +208,6 @@ ipset_add_rrset_data(struct ipset_env *i > ret = add_to_ipset((filter_dev)ie->dev, setname, > rr_data + 2, af); > if (ret < 0) { > log_err("ipset: could not add %s into %s", > dname, setname); > - > -#if HAVE_NET_PFVAR_H > - /* don't close as we might not be able to > open again due to dropped privs */ > -#else > - mnl_socket_close((filter_dev)ie->dev); > - ie->dev = NULL; > -#endif > break; > } > } > @@ -226,15 +219,15 @@ ipset_check_zones_for_rrset(struct modul > struct ub_packed_rrset_key *rrset, const char *qname, int qlen, > const char *setname, int af) > { > - static char dname[BUFF_LEN]; > + char dname[LDNS_MAX_DOMAINLEN*4+16]; > const char *ds, *qs; > int dlen, plen; > > struct config_strlist *p; > struct packed_rrset_data *d; > > - dlen = sldns_wire2str_dname_buf(rrset->rk.dname, > rrset->rk.dname_len, dname, BUFF_LEN); > - if (dlen == 0) { > + dlen = sldns_wire2str_dname_buf(rrset->rk.dname, > rrset->rk.dname_len, dname, sizeof(dname)); > + if (dlen == 0 || dlen >= (int)sizeof(dname)) { > log_err("bad domain name"); > return -1; > } > @@ -276,7 +269,7 @@ static int ipset_update(struct module_en > const char *setname; > struct ub_packed_rrset_key *rrset; > int af; > - static char qname[BUFF_LEN]; > + char qname[LDNS_MAX_DOMAINLEN*4+16]; > int qlen; > > #ifdef HAVE_NET_PFVAR_H > @@ -292,8 +285,8 @@ static int ipset_update(struct module_en > #endif > > qlen = sldns_wire2str_dname_buf(qinfo.qname, qinfo.qname_len, > - qname, BUFF_LEN); > - if(qlen == 0) { > + qname, sizeof(qname)); > + if(qlen == 0 || qlen >= (int)sizeof(qname)) { > log_err("bad domain name"); > return -1; > } > @@ -351,7 +344,7 @@ void ipset_destartup(struct module_env* > if (!env || !env->modinfo[id]) { > return; > } > - ipset_env = (struct ipset_env *)env->modinfo[id]; > + ipset_env = (struct ipset_env*)env->modinfo[id]; > > dev = (filter_dev)ipset_env->dev; > if (dev) { > @@ -372,6 +365,16 @@ int ipset_init(struct module_env* env, i > > ipset_env->name_v4 = env->cfg->ipset_name_v4; > ipset_env->name_v6 = env->cfg->ipset_name_v6; > +#ifndef HAVE_NET_PFVAR_H > + if (ipset_env->name_v4 && strlen(ipset_env->name_v4) >= > IPSET_MAXNAMELEN) { > + log_err("ipset: name-v4 exceeds IPSET_MAXNAMELEN (%d)", > IPSET_MAXNAMELEN); > + return 0; > + } > + if (ipset_env->name_v6 && strlen(ipset_env->name_v6) >= > IPSET_MAXNAMELEN) { > + log_err("ipset: name-v6 exceeds IPSET_MAXNAMELEN (%d)", > IPSET_MAXNAMELEN); > + return 0; > + } > +#endif > > ipset_env->v4_enabled = !ipset_env->name_v4 || > (strlen(ipset_env->name_v4) == 0) ? 0 : 1; > ipset_env->v6_enabled = !ipset_env->name_v6 || > (strlen(ipset_env->name_v6) == 0) ? 0 : 1; > Index: iterator/iter_delegpt.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_delegpt.c,v > diff -u -p -r1.9 iter_delegpt.c > --- iterator/iter_delegpt.c 31 Aug 2025 21:41:09 -0000 1.9 > +++ iterator/iter_delegpt.c 20 Sep 2026 09:50:48 -0000 > @@ -118,10 +118,10 @@ delegpt_add_ns(struct delegpt* dp, struc > sizeof(struct delegpt_ns)); > if(!ns) > return 0; > - ns->next = dp->nslist; > ns->namelen = len; > - dp->nslist = ns; > ns->name = regional_alloc_init(region, name, ns->namelen); > + if(!ns->name) > + return 0; > ns->cache_lookup_count = 0; > ns->resolved = 0; > ns->got4 = 0; > @@ -137,7 +137,9 @@ delegpt_add_ns(struct delegpt* dp, struc > } else { > ns->tls_auth_name = NULL; > } > - return ns->name != 0; > + ns->next = dp->nslist; > + dp->nslist = ns; > + return 1; > } > > struct delegpt_ns* > @@ -223,11 +225,7 @@ delegpt_add_addr(struct delegpt* dp, str > sizeof(struct delegpt_addr)); > if(!a) > return 0; > - a->next_target = dp->target_list; > - dp->target_list = a; > a->next_result = 0; > - a->next_usable = dp->usable_list; > - dp->usable_list = a; > memcpy(&a->addr, addr, addrlen); > a->addrlen = addrlen; > a->attempts = 0; > @@ -241,6 +239,10 @@ delegpt_add_addr(struct delegpt* dp, str > } else { > a->tls_auth_name = NULL; > } > + a->next_target = dp->target_list; > + dp->target_list = a; > + a->next_usable = dp->usable_list; > + dp->usable_list = a; > return 1; > } > > @@ -398,30 +400,33 @@ delegpt_count_missing_targets(struct del > > /** find NS rrset in given list */ > static struct ub_packed_rrset_key* > -find_NS(struct reply_info* rep, size_t from, size_t to) > +find_NS(struct reply_info* rep, size_t from, size_t to, uint16_t qclass) > { > size_t i; > for(i=from; i - if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NS) > + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NS && > + ntohs(rep->rrsets[i]->rk.rrset_class) == qclass) > return rep->rrsets[i]; > } > return NULL; > } > > 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; > size_t i; > /* look for NS records in the authority section... */ > ns_rrset = find_NS(msg->rep, msg->rep->an_numrrsets, > - msg->rep->an_numrrsets+msg->rep->ns_numrrsets); > + msg->rep->an_numrrsets+msg->rep->ns_numrrsets, > + msg->qinfo.qclass); > > /* In some cases (even legitimate, perfectly legal cases), the > * NS set for the "referral" might be in the answer section. */ > if(!ns_rrset) > - ns_rrset = find_NS(msg->rep, 0, msg->rep->an_numrrsets); > + ns_rrset = find_NS(msg->rep, 0, msg->rep->an_numrrsets, > + msg->qinfo.qclass); > > /* If there was no NS rrset in the authority section, then this > * wasn't a referral message. (It might not actually be a > @@ -436,7 +441,7 @@ delegpt_from_message(struct dns_msg* msg > 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 */ > @@ -447,10 +452,12 @@ delegpt_from_message(struct dns_msg* msg > i < > (msg->rep->an_numrrsets+msg->rep->ns_numrrsets)) > continue; > > - if(ntohs(s->rk.type) == LDNS_RR_TYPE_A) { > + if(ntohs(s->rk.type) == LDNS_RR_TYPE_A && > + ntohs(s->rk.rrset_class) == msg->qinfo.qclass) { > if(!delegpt_add_rrset_A(dp, region, s, 0, NULL)) > return NULL; > - } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_AAAA) { > + } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_AAAA && > + ntohs(s->rk.rrset_class) == msg->qinfo.qclass) { > if(!delegpt_add_rrset_AAAA(dp, region, s, 0, NULL)) > return NULL; > } > @@ -460,7 +467,7 @@ delegpt_from_message(struct dns_msg* msg > > 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; > @@ -475,7 +482,7 @@ delegpt_rrset_add_ns(struct delegpt* dp, > 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; > @@ -534,7 +541,7 @@ delegpt_add_rrset(struct delegpt* dp, st > 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) > @@ -659,8 +666,6 @@ int delegpt_add_ns_mlc(struct delegpt* d > free(ns); > return 0; > } > - ns->next = dp->nslist; > - dp->nslist = ns; > ns->cache_lookup_count = 0; > ns->resolved = 0; > ns->got4 = 0; > @@ -679,6 +684,8 @@ int delegpt_add_ns_mlc(struct delegpt* d > } else { > ns->tls_auth_name = NULL; > } > + ns->next = dp->nslist; > + dp->nslist = ns; > return 1; > } > > @@ -704,11 +711,7 @@ int delegpt_add_addr_mlc(struct delegpt* > a = (struct delegpt_addr*)malloc(sizeof(struct delegpt_addr)); > if(!a) > return 0; > - a->next_target = dp->target_list; > - dp->target_list = a; > a->next_result = 0; > - a->next_usable = dp->usable_list; > - dp->usable_list = a; > memcpy(&a->addr, addr, addrlen); > a->addrlen = addrlen; > a->attempts = 0; > @@ -724,6 +727,10 @@ int delegpt_add_addr_mlc(struct delegpt* > } else { > a->tls_auth_name = NULL; > } > + a->next_target = dp->target_list; > + dp->target_list = a; > + a->next_usable = dp->usable_list; > + dp->usable_list = a; > return 1; > } > > Index: iterator/iter_delegpt.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_delegpt.h,v > diff -u -p -r1.12 iter_delegpt.h > --- iterator/iter_delegpt.h 26 Sep 2025 07:32:37 -0000 1.12 > +++ iterator/iter_delegpt.h 20 Sep 2026 09:50:48 -0000 > @@ -221,10 +221,11 @@ int delegpt_add_ns(struct delegpt* dp, s > * @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 dele > * > * @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. > Index: iterator/iter_resptype.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_resptype.c,v > diff -u -p -r1.7 iter_resptype.c > --- iterator/iter_resptype.c 12 Apr 2024 15:45:24 -0000 1.7 > +++ iterator/iter_resptype.c 20 Sep 2026 09:50:48 -0000 > @@ -107,7 +107,7 @@ response_type_from_cache(struct dns_msg* > enum response_type > response_type_from_server(int rdset, > struct dns_msg* msg, struct query_info* request, struct delegpt* > dp, > - int* empty_nodata_found) > + int* empty_nodata_found, int msg_lame_empty, int msg_lame_referral) > { > uint8_t* origzone = (uint8_t*)"\000"; /* the default */ > struct ub_packed_rrset_key* s; > @@ -122,6 +122,10 @@ response_type_from_server(int rdset, > > /* If the message is NXDOMAIN, then it answers the question. */ > if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NXDOMAIN) { > + if(msg->rep->an_numrrsets == 0 && > + msg->rep->ns_numrrsets == 0 && > + msg_lame_empty) > + return RESPONSE_TYPE_LAME; > /* make sure its not recursive when we don't want it to */ > if( (msg->rep->flags&BIT_RA) && > !(msg->rep->flags&BIT_AA) && !rdset) > @@ -143,6 +147,10 @@ response_type_from_server(int rdset, > if(FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR) > return RESPONSE_TYPE_THROWAWAY; > > + if(msg->rep->an_numrrsets == 0 && msg->rep->ns_numrrsets == 0 && > + msg_lame_empty) > + return RESPONSE_TYPE_LAME; > + > /* Note: TC bit has already been handled */ > > if(dp) { > @@ -249,13 +257,16 @@ response_type_from_server(int rdset, > * which gives ns==zone delegation from > cache > * without AA bit as well, with nodata > nosoa*/ > /* real answer must be +AA and SOA > RFC(2308), > - * so this is wrong, and we SERVFAIL it if > - * this is the only possible reply, if it > - * is misdeployed the THROWAWAY makes us > pick > - * the next server from the selection */ > - if(msg->rep->an_numrrsets==0 && > + * this is picked up as lame_referral by > the > + * sanitize step, so it can spot if there > + * was data in the answer section before > + * removal. If such data is then removed we > + * do not want to turn that answer into > lame. > + * But if it was not there, it can be > lame. */ > + if(msg_lame_referral && > + msg->rep->an_numrrsets==0 && > !(msg->rep->flags&BIT_AA) && > !rdset) > - return RESPONSE_TYPE_THROWAWAY; > + return RESPONSE_TYPE_LAME; > return RESPONSE_TYPE_ANSWER; > } > /* If we are getting a referral upwards (or to > Index: iterator/iter_resptype.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_resptype.h,v > diff -u -p -r1.1.1.3 iter_resptype.h > --- iterator/iter_resptype.h 12 Apr 2024 15:44:27 -0000 1.1.1.3 > +++ iterator/iter_resptype.h 20 Sep 2026 09:50:48 -0000 > @@ -120,10 +120,14 @@ enum response_type response_type_from_ca > * @param dp: The delegation point that was being queried > * when the response was returned. > * @param empty_nodata_found: flag to keep track of empty nodata > detection. > + * @param msg_lame_empty: The scrubber indicates that this empty message > + * is lame, before it became empty. > + * @param msg_lame_referral: returned true if the reply has a referral > before > + * scrub. > * @return the response type (CNAME or ANSWER). > */ > enum response_type response_type_from_server(int rdset, > struct dns_msg* msg, struct query_info* request, struct delegpt* > dp, > - int* empty_nodata_found); > + int* empty_nodata_found, int msg_lame_empty, int > msg_lame_referral); > > #endif /* ITERATOR_ITER_RESPTYPE_H */ > Index: iterator/iter_scrub.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_scrub.c,v > diff -u -p -r1.21 iter_scrub.c > --- iterator/iter_scrub.c 26 May 2026 11:14:11 -0000 1.21 > +++ iterator/iter_scrub.c 20 Sep 2026 09:50:48 -0000 > @@ -294,7 +294,14 @@ synth_cname_rrset(uint8_t** sname, size_ > if(ttl_t > MAX_TTL) ttl_t = MAX_TTL; > ttl = (uint32_t)ttl_t; > sldns_write_uint32(cn->rr_first->ttl_data, ttl); > - sldns_write_uint32(rrset->rr_first->ttl_data, ttl); > + /* Do NOT write the clamp back into the packet buffer: > + * parse_packet already sized every name from the original > + * bytes and rdata_copy re-walks them trusting those sizes; > + * mutating packet bytes between the walks breaks that > + * invariant (compression pointers can target these TTL > + * bytes). The DNAME rrset receives the same clamp at store > + * time in rdata_copy, so the DNAME and the synthesized > + * CNAME still carry equal TTLs in the cache. */ > } > sldns_write_uint16(cn->rr_first->ttl_data+4, aliaslen); > memmove(cn->rr_first->ttl_data+6, alias, aliaslen); > @@ -316,6 +323,20 @@ synth_cname_rrset(uint8_t** sname, size_ > return cn; > } > > +/** Check if the packet has type NS in answer or authority section */ > +static int > +pkt_contains_ns(struct msg_parse* msg) > +{ > + struct rrset_parse* rrset; > + for(rrset = msg->rrset_first; rrset; rrset = > rrset->rrset_all_next) { > + if(rrset->type == LDNS_RR_TYPE_NS && > + (rrset->section == LDNS_SECTION_ANSWER || > + rrset->section == LDNS_SECTION_AUTHORITY)) > + return 1; > + } > + return 0; > +} > + > /** check if DNAME applies to a name */ > static int > pkt_strict_sub(sldns_buffer* pkt, uint8_t* sname, uint8_t* dr) > @@ -394,6 +415,8 @@ shorten_rrset(sldns_buffer* pkt, struct > struct rr_parse* rr = rrset->rr_first, *prev = NULL; > if(!rr) > return; > + if(count < 1) > + return; /* cannot leave a still-linked rrset_parse with > rr_count == 0 */ > for(i=0; i prev = rr; > rr = rr->next; > @@ -478,6 +501,7 @@ scrub_normalize(sldns_buffer* pkt, struc > size_t snamelen = qinfo->qname_len; > struct rrset_parse* rrset, *prev, *nsset=NULL; > int cname_length = 0; /* number of CNAMEs, or DNAMEs */ > + int has_answer = 0; /* if answer section contains > nonCNAME,nonDNAME */ > > if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR && > FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN && > @@ -519,6 +543,11 @@ scrub_normalize(sldns_buffer* pkt, struc > (unsigned)rrset->rr_count); > return 0; > } > + if(has_answer) { > + remove_rrset("normalize: removing DNAME > redirection after answer:", > + pkt, msg, prev, &rrset); > + continue; > + } > if(!synth_cname(sname, snamelen, rrset, alias, > &aliaslen, pkt)) { > verbose(VERB_ALGO, "synthesized CNAME " > @@ -569,6 +598,11 @@ scrub_normalize(sldns_buffer* pkt, struc > if(rrset->type == LDNS_RR_TYPE_CNAME) { > struct rrset_parse* nx = rrset->rrset_all_next; > uint8_t* oldsname = sname; > + if(has_answer) { > + remove_rrset("normalize: removing > redirection after answer:", > + pkt, msg, prev, &rrset); > + continue; > + } > cname_length++; > /* see if the next one is a DNAME, if so, swap > them */ > if(nx && nx->section == LDNS_SECTION_ANSWER && > @@ -621,6 +655,9 @@ scrub_normalize(sldns_buffer* pkt, struc > if(rrset->type == LDNS_RR_TYPE_NS > && > rrset->rr_count > > env->cfg->iter_scrub_ns) { > shorten_rrset(pkt, rrset, > env->cfg->iter_scrub_ns); > + } else if(rrset->type == > LDNS_RR_TYPE_DS && > + rrset->rr_count > > env->cfg->iter_scrub_ns) { > + shorten_rrset(pkt, rrset, > env->cfg->iter_scrub_ns); > } > prev = rrset; > rrset = rrset->rrset_all_next; > @@ -640,6 +677,9 @@ scrub_normalize(sldns_buffer* pkt, struc > if(rrset->type == LDNS_RR_TYPE_NS && > rrset->rr_count > env->cfg->iter_scrub_ns) { > shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); > + } else if(rrset->type == LDNS_RR_TYPE_DS && > + rrset->rr_count > env->cfg->iter_scrub_ns) { > + shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); > } > > /* Mark the additional names from relevant rrset as OK. */ > @@ -647,6 +687,7 @@ scrub_normalize(sldns_buffer* pkt, struc > * will be removed by sanitize, so no additional for them > */ > if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0) > mark_additional_rrset(pkt, msg, rrset); > + has_answer = 1; > > prev = rrset; > rrset = rrset->rrset_all_next; > @@ -732,6 +773,11 @@ scrub_normalize(sldns_buffer* pkt, struc > "RRset:", pkt, msg, prev, &rrset); > continue; > } > + if(ntohs(rrset->rrset_class) != qinfo->qclass) { > + remove_rrset("normalize: removing other > class " > + "RRset:", pkt, msg, prev, &rrset); > + continue; > + } > if(nsset == NULL) { > nsset = rrset; > } else { > @@ -758,6 +804,11 @@ scrub_normalize(sldns_buffer* pkt, struc > shorten_rrset(pkt, rrset, > env->cfg->iter_scrub_ns); > } > } > + } else if(rrset->type==LDNS_RR_TYPE_DS) { > + if(rrset->rr_count > env->cfg->iter_scrub_ns) { > + shorten_rrset(pkt, rrset, > + env->cfg->iter_scrub_ns); > + } > } > /* if this is type DS and we query for type DS we just got > * a referral answer for our type DS query, fix packet */ > @@ -968,12 +1019,20 @@ scrub_sanitize_rr_length(sldns_buffer* p > * @param env: module environment with config and cache. > * @param ie: iterator environment with private address data. > * @param qstate: for setting errinf for EDE error messages. > + * @param pkt_before_NS: if the packet had type NS before scrub. If that > + * is removed now, that indicates this may have been lame. > + * @param msg_lame_empty: returned true if the empty packet is lame. > + * @param msg_lame_referral: returned true if the reply has a referral > before > + * scrub. > + * @param rdset: if RD bit was sent in query sent by unbound. > * @return 0 on error. > */ > static int > scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg, > struct query_info* qinfo, uint8_t* zonename, struct module_env* > env, > - struct iter_env* ie, struct module_qstate* qstate) > + struct iter_env* ie, struct module_qstate* qstate, > + int pkt_before_NS, int* msg_lame_empty, int* msg_lame_referral, > + int rdset) > { > int del_addi = 0; /* if additional-holding rrsets are deleted, we > do not trust the normalized additional-A-AAAA any more */ > @@ -1130,6 +1189,21 @@ scrub_sanitize(sldns_buffer* pkt, struct > prev = rrset; > rrset = rrset->rrset_all_next; > } > + > + /* If the packet is empty now, but it was not before. And there > + * was type NS in authority, then that indicates the answer is > lame. */ > + if(msg->rrset_first == NULL && pkt_before_NS) { > + *msg_lame_empty = 1; > + verbose(VERB_ALGO, "sanitize: empty message had referral > to NS before, marked as lame"); > + } else if(pkt_before_NS && msg->an_rrsets==0 && > + !(msg->flags&BIT_AA) && !rdset) { > + /* If the packet is now a referral, not really a nodata, > + * then if it was also with an empty answer section before, > + * it is also lame. */ > + *msg_lame_referral = 1; > + verbose(VERB_ALGO, "sanitize: message has referral not > answer, marked as lame"); > + } > + > return 1; > } > > @@ -1137,11 +1211,15 @@ int > scrub_message(sldns_buffer* pkt, struct msg_parse* msg, > struct query_info* qinfo, uint8_t* zonename, struct regional* > region, > struct module_env* env, struct module_qstate* qstate, > - struct iter_env* ie) > + struct iter_env* ie, int* msg_lame_empty, int* msg_lame_referral, > + int rdset) > { > + int pkt_before_NS; > /* basic sanity checks */ > log_nametypeclass(VERB_ALGO, "scrub for", zonename, > LDNS_RR_TYPE_NS, > qinfo->qclass); > + *msg_lame_empty = 0; > + *msg_lame_referral = 0; > if(msg->qdcount > 1) > return 0; > if( !(msg->flags&BIT_QR) ) > @@ -1166,11 +1244,21 @@ scrub_message(sldns_buffer* pkt, struct > return 0; > } > > + /* If the packet contains type NS in authority before scrub, > + * like a self referral. With the answer section empty, it > + * was not AA, the query was not sent with RD, with NS in auth, > + * and no SOA in auth. For a negative answer, type SOA is present. > + * This detects certain lameness if after has removed that. */ > + pkt_before_NS = msg->an_rrsets == 0 && > + !(msg->flags&BIT_AA) && !rdset && > + pkt_contains_ns(msg) && !soa_in_auth(msg); > + > /* normalize the response, this cleans up the additional. */ > 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)) > + if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate, > + pkt_before_NS, msg_lame_empty, msg_lame_referral, rdset)) > return 0; > return 1; > } > Index: iterator/iter_scrub.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_scrub.h,v > diff -u -p -r1.1.1.3 iter_scrub.h > --- iterator/iter_scrub.h 12 Apr 2024 15:44:27 -0000 1.1.1.3 > +++ iterator/iter_scrub.h 20 Sep 2026 09:50:48 -0000 > @@ -62,11 +62,16 @@ struct module_qstate; > * @param env: module environment with config settings and cache. > * @param qstate: for setting errinf for EDE error messages. > * @param ie: iterator module environment data. > + * @param msg_lame_empty: returned true if the empty packet is lame. > + * @param msg_lame_referral: returned true if the reply has a referral > before > + * scrub. > + * @param rdset: if RD bit was sent in query sent by unbound. > * @return: false if the message is total waste. true if scrubbed with > success. > */ > int scrub_message(struct sldns_buffer* pkt, struct msg_parse* msg, > struct query_info* qinfo, uint8_t* zonename, struct regional* > regional, > struct module_env* env, struct module_qstate* qstate, > - struct iter_env* ie); > + struct iter_env* ie, int* msg_lame_empty, int* msg_lame_referral, > + int rdset); > > #endif /* ITERATOR_ITER_SCRUB_H */ > Index: iterator/iter_utils.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_utils.c,v > diff -u -p -r1.26 iter_utils.c > --- iterator/iter_utils.c 26 May 2026 11:14:11 -0000 1.26 > +++ iterator/iter_utils.c 20 Sep 2026 09:50:48 -0000 > @@ -1313,7 +1313,8 @@ iter_lookup_parent_NS_from_cache(struct > 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; > } > @@ -1702,4 +1703,12 @@ iter_make_minimal(struct reply_info* rep > rep->ns_numrrsets = 0; > 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; > } > Index: iterator/iter_utils.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/iterator/iter_utils.h,v > diff -u -p -r1.19 iter_utils.h > --- iterator/iter_utils.h 26 May 2026 11:14:11 -0000 1.19 > +++ iterator/iter_utils.h 20 Sep 2026 09:50:48 -0000 > @@ -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 */ > Index: iterator/iterator.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/iterator/iterator.c,v > diff -u -p -r1.43 iterator.c > --- iterator/iterator.c 27 Jul 2026 14:14:39 -0000 1.43 > +++ iterator/iterator.c 20 Sep 2026 09:50:48 -0000 > @@ -1511,6 +1511,7 @@ processInitRequest(struct module_qstate* > verbose(VERB_ALGO, "no-cache set, going to the network"); > qstate->no_cache_lookup = 1; > qstate->no_cache_store = 1; > + qstate->fwd_stub_no_cache = 1; > msg = NULL; > } else if(qstate->blacklist) { > /* if cache, or anything else, was blacklisted then > @@ -1530,7 +1531,7 @@ processInitRequest(struct module_qstate* > 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 > @@ -2108,7 +2109,7 @@ query_for_targets(struct module_qstate* > ns->resolved = 1; > } > break; > - } > + } > } > /* Send the A request. */ > if((ie->supports_ipv4 || ie->nat64.use_nat64) && > @@ -2130,7 +2131,7 @@ query_for_targets(struct module_qstate* > * a missing target. */ > ns->resolved = 1; > break; > - } > + } > } > > /* mark this target as in progress. */ > @@ -2255,7 +2256,7 @@ processLastResort(struct module_qstate* > errinf(qstate, "could not fetch nameserver"); > errinf_dname(qstate, "at zone", iq->dp->name); > if(ret == 1) > - return error_response(qstate, id, > LDNS_RCODE_SERVFAIL); > + return error_response(qstate, id, > LDNS_RCODE_SERVFAIL); > return error_response_cache(qstate, id, > LDNS_RCODE_SERVFAIL); > } > iq->num_target_queries += qs; > @@ -2391,6 +2392,12 @@ processDSNSFind(struct module_qstate* qs > > /* go up one (more) step, until we hit the dp, if so, end */ > dname_remove_label(&iq->dsns_point, &iq->dsns_point_len); > + if(++iq->dsns_count > MAX_DSNS_FIND_COUNT) { > + verbose(VERB_QUERY, "DS NS search exceeded %d labels", > + MAX_DSNS_FIND_COUNT); > + errinf(qstate, "DS NS search exceeded label limit"); > + return error_response_cache(qstate, id, > LDNS_RCODE_SERVFAIL); > + } > if(query_dname_compare(iq->dsns_point, iq->dp->name) == 0) { > /* there was no inbetween nameserver, use the old > delegation > * point again. And this time, because dsns_point is > nonNULL > @@ -2809,7 +2816,7 @@ processQueryTargets(struct module_qstate > if((ret=query_for_targets(qstate, iq, ie, id, -1, > &extra))!=0) { > errinf(qstate, "could not fetch nameservers for > 0x20 fallback"); > if(ret == 1) > - return error_response(qstate, id, > LDNS_RCODE_SERVFAIL); > + return error_response(qstate, id, > LDNS_RCODE_SERVFAIL); > return error_response_cache(qstate, id, > LDNS_RCODE_SERVFAIL); > } > iq->num_target_queries += extra; > @@ -2961,8 +2968,8 @@ processQueryTargets(struct module_qstate > errinf(qstate, "could not fetch > nameserver"); > errinf_dname(qstate, "at zone", > iq->dp->name); > if(ret == 1) > - return error_response(qstate, id, > - LDNS_RCODE_SERVFAIL); > + return > error_response(qstate, id, > + > LDNS_RCODE_SERVFAIL); > return > error_response_cache(qstate, id, > LDNS_RCODE_SERVFAIL); > } > @@ -3073,7 +3080,9 @@ processQueryTargets(struct module_qstate > > /* Do not check ratelimit for forwarding queries or if we already > got a > * pass. */ > - sq_check_ratelimit = (!(iq->chase_flags & BIT_RD) && > !iq->ratelimit_ok); > + sq_check_ratelimit = ((!(iq->chase_flags & BIT_RD) && > + !iq->ratelimit_ok)); > + iq->ratelimit_incremented = 0; > /* We have a valid target. */ > if(verbosity >= VERB_QUERY) { > log_query_info(VERB_QUERY, "sending query:", > &iq->qinfo_out); > @@ -3099,7 +3108,8 @@ processQueryTargets(struct module_qstate > iq->dp->name, iq->dp->namelen, > (iq->dp->tcp_upstream || qstate->env->cfg->tcp_upstream), > (iq->dp->ssl_upstream || qstate->env->cfg->ssl_upstream), > - target->tls_auth_name, qstate, &sq_was_ratelimited); > + target->tls_auth_name, qstate, &sq_was_ratelimited, > + &iq->ratelimit_incremented); > if(!outq) { > if(sq_was_ratelimited) { > lock_basic_lock(&ie->queries_ratelimit_lock); > @@ -3137,7 +3147,6 @@ find_NS(struct reply_info* rep, size_t f > 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 > @@ -3179,7 +3188,8 @@ processQueryResponse(struct module_qstat > orig_empty_nodata_found = iq->empty_nodata_found; > type = response_type_from_server( > (int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd), > - iq->response, &iq->qinfo_out, iq->dp, > &iq->empty_nodata_found); > + iq->response, &iq->qinfo_out, iq->dp, > &iq->empty_nodata_found, > + iq->msg_lame_empty, iq->msg_lame_referral); > iq->chase_to_rd = 0; > /* remove TC flag, if this is erroneously set by TCP upstream */ > iq->response->rep->flags &= ~BIT_TC; > @@ -3457,7 +3467,14 @@ processQueryResponse(struct module_qstat > iq->deleg_msg = iq->response; > /* Keep current delegation point for label comparison */ > old_dp = iq->dp; > - iq->dp = delegpt_from_message(iq->response, > qstate->region); > + /* A referral reply is "pleasant", refund the > + * parent dp's rate charge before descending to the child. > */ > + if(iq->ratelimit_incremented) > + 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, > + deleg_port_number(qstate->env)); > if (qstate->env->cfg->qname_minimisation) > iq->minimisation_state = INIT_MINIMISE_STATE; > if(!iq->dp) { > @@ -3734,7 +3751,8 @@ prime_supers(struct module_qstate* qstat > 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. */ > @@ -3785,7 +3803,8 @@ processPrimeResponse(struct module_qstat > iq->response->rep->flags &= ~(BIT_RD|BIT_RA); /* ignore rec-lame */ > type = response_type_from_server( > (int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd), > - iq->response, &iq->qchase, iq->dp, NULL); > + iq->response, &iq->qchase, iq->dp, NULL, > iq->msg_lame_empty, > + iq->msg_lame_referral); > if(type == RESPONSE_TYPE_ANSWER) { > qstate->return_rcode = LDNS_RCODE_NOERROR; > qstate->return_msg = iq->response; > @@ -3949,7 +3968,8 @@ processDSNSResponse(struct module_qstate > > /* 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"); > @@ -3998,7 +4018,7 @@ processClassResponse(struct module_qstat > /* if there are records, copy RCODE */ > /* lower sec_state if this message is lower */ > if(from->rep->rrset_count != 0) { > - size_t n = > from->rep->rrset_count+to->rep->rrset_count; > + size_t i, n = > from->rep->rrset_count+to->rep->rrset_count; > struct ub_packed_rrset_key** dest, **d; > /* copy appropriate rcode */ > to->rep->flags = from->rep->flags; > @@ -4020,24 +4040,49 @@ processClassResponse(struct module_qstat > memcpy(dest, to->rep->rrsets, to->rep->an_numrrsets > * sizeof(dest[0])); > dest += to->rep->an_numrrsets; > - memcpy(dest, from->rep->rrsets, > from->rep->an_numrrsets > - * sizeof(dest[0])); > + for(i=0; irep->an_numrrsets; i++) { > + dest[i] = packed_rrset_copy_region( > + from->rep->rrsets[i], > forq->region, 0); > + if(!dest[i]) { > + log_err("malloc failed in collect > ANY"); > + foriq->state = FINISHED_STATE; > + return; > + } > + } > dest += from->rep->an_numrrsets; > /* copy NS */ > memcpy(dest, to->rep->rrsets+to->rep->an_numrrsets, > to->rep->ns_numrrsets * sizeof(dest[0])); > dest += to->rep->ns_numrrsets; > - memcpy(dest, > from->rep->rrsets+from->rep->an_numrrsets, > - from->rep->ns_numrrsets * sizeof(dest[0])); > + for(i=0; irep->ns_numrrsets; i++) { > + dest[i] = packed_rrset_copy_region( > + from->rep->rrsets[ > + from->rep->an_numrrsets+i], > + forq->region, 0); > + if(!dest[i]) { > + log_err("malloc failed in collect > ANY"); > + foriq->state = FINISHED_STATE; > + return; > + } > + } > dest += from->rep->ns_numrrsets; > /* copy AR */ > memcpy(dest, to->rep->rrsets+to->rep->an_numrrsets+ > to->rep->ns_numrrsets, > to->rep->ar_numrrsets * sizeof(dest[0])); > dest += to->rep->ar_numrrsets; > - memcpy(dest, > from->rep->rrsets+from->rep->an_numrrsets+ > - from->rep->ns_numrrsets, > - from->rep->ar_numrrsets * sizeof(dest[0])); > + for(i=0; irep->ar_numrrsets; i++) { > + dest[i] = packed_rrset_copy_region( > + from->rep->rrsets[ > + from->rep->an_numrrsets+ > + from->rep->ns_numrrsets+i], > + forq->region, 0); > + if(!dest[i]) { > + log_err("malloc failed in collect > ANY"); > + foriq->state = FINISHED_STATE; > + return; > + } > + } > /* update counts */ > to->rep->rrsets = d; > to->rep->an_numrrsets += from->rep->an_numrrsets; > @@ -4395,7 +4440,10 @@ process_response(struct module_qstate* q > > /* normalize and sanitize: easy to delete items from linked lists > */ > if(!scrub_message(pkt, prs, &iq->qinfo_out, iq->dp->name, > - qstate->env->scratch, qstate->env, qstate, ie)) { > + qstate->env->scratch, qstate->env, qstate, ie, > + &iq->msg_lame_empty, &iq->msg_lame_referral, > + (int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd) > + )) { > /* if 0x20 enabled, start fallback, but we have no message > */ > if(event == module_event_capsfail && !iq->caps_fallback) { > iq->caps_fallback = 1; > Index: iterator/iterator.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/iterator/iterator.h,v > diff -u -p -r1.24 iterator.h > --- iterator/iterator.h 31 Aug 2025 21:41:09 -0000 1.24 > +++ iterator/iterator.h 20 Sep 2026 09:50:48 -0000 > @@ -104,6 +104,11 @@ extern int BLACKLIST_PENALTY; > #define RTT_BAND 400 > /** Number of retries for empty nodata packets before it is accepted. */ > #define EMPTY_NODATA_RETRY_COUNT 2 > +/** max label-strip iterations in DSNS_FIND_STATE (RFC 4035 4.2 parent-NS > + * search) before giving up; bounds upstream NS sends per client DS. > + * Means the max number of labels in grandchild to the grandparent zone > that > + * are co-hosted. */ > +#define MAX_DSNS_FIND_COUNT 20 > > /** > * Iterator global state for nat64. > @@ -375,6 +380,10 @@ struct iter_qstate { > /** if true, already tested for ratelimiting and passed the test */ > int ratelimit_ok; > > + /** If the last query, that may be a referral, incremented the > + * ratelimit counter. */ > + int ratelimit_incremented; > + > /** > * The query must store NS records from referrals as parentside RRs > * Enabled once it hits resolution problems, to throttle retries. > @@ -399,6 +408,8 @@ struct iter_qstate { > uint8_t* dsns_point; > /** length of the dname in dsns_point */ > size_t dsns_point_len; > + /** number of label-strip iterations performed in DSNS_FIND_STATE > */ > + int dsns_count; > > /** > * expected dnssec information for this iteration step. > @@ -433,6 +444,13 @@ struct iter_qstate { > * This flag detects that a completely empty nodata was received, > * already so that it is accepted later. */ > int empty_nodata_found; > + > + /** Store if the answer was empty, but lame, before it became > empty.*/ > + int msg_lame_empty; > + > + /** Store if the answer was a referral, to self, before scrub. So > the > + * it is not some sort of answer. */ > + int msg_lame_referral; > > /** list of pending queries to authoritative servers. */ > struct outbound_list outlist; > Index: libunbound/context.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/libunbound/context.h,v > diff -u -p -r1.9 context.h > --- libunbound/context.h 5 Sep 2023 11:12:10 -0000 1.9 > +++ libunbound/context.h 20 Sep 2026 09:50:48 -0000 > @@ -167,6 +167,8 @@ struct ctx_query { > ub_event_callback_type cb_event; > /** for async query, the callback user arg */ > void* cb_arg; > + /** for async query the unique info */ > + void* unique_info; > > /** answer message, result from resolver lookup. */ > uint8_t* msg; > Index: libunbound/libunbound.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/libunbound/libunbound.c,v > diff -u -p -r1.22 libunbound.c > --- libunbound/libunbound.c 4 Sep 2024 09:36:40 -0000 1.22 > +++ libunbound/libunbound.c 20 Sep 2026 09:50:48 -0000 > @@ -571,6 +571,8 @@ ub_ctx_async(struct ub_ctx* ctx, int dot > int > ub_poll(struct ub_ctx* ctx) > { > + if(!ctx || ctx->event_base) > + return UB_INITFAIL; > /* no need to hold lock while testing for readability. */ > return tube_poll(ctx->rr_pipe); > } > @@ -578,6 +580,8 @@ ub_poll(struct ub_ctx* ctx) > int > ub_fd(struct ub_ctx* ctx) > { > + if(!ctx || ctx->event_base) > + return -1; > return tube_read_fd(ctx->rr_pipe); > } > > @@ -672,6 +676,8 @@ ub_process(struct ub_ctx* ctx) > int r; > uint8_t* msg; > uint32_t len; > + if(!ctx || ctx->event_base) > + return UB_INITFAIL; > while(1) { > msg = NULL; > lock_basic_lock(&ctx->rrpipe_lock); > @@ -700,6 +706,8 @@ ub_wait(struct ub_ctx* ctx) > int r; > uint8_t* msg; > uint32_t len; > + if(!ctx || ctx->event_base) > + return UB_INITFAIL; > /* this is basically the same loop as _process(), but with changes. > * holds the rrpipe lock and waits with tube_wait */ > while(1) { > @@ -837,6 +845,8 @@ ub_resolve_async(struct ub_ctx* ctx, con > struct ctx_query* q; > uint8_t* msg = NULL; > uint32_t len = 0; > + if(!ctx || ctx->event_base) > + return UB_INITFAIL; > > if(async_id) > *async_id = 0; > @@ -1467,8 +1477,15 @@ ub_ctx_set_event(struct ub_ctx* ctx, str > > lock_basic_lock(&ctx->cfglock); > /* destroy the current worker - safe to pass in NULL */ > + > + /* Unlock the cfglock during libworker_delete_event, since it > + * calls context_release_alloc, that wants to lock cfglock again. > + * Since the event base is used from one thread, the one that > + * called this function, it is safe to do so. */ > + lock_basic_unlock(&ctx->cfglock); > libworker_delete_event(ctx->event_worker); > ctx->event_worker = NULL; > + lock_basic_lock(&ctx->cfglock); > new_base = ub_libevent_event_base(base); > if (new_base) > ctx->event_base = new_base; > Index: libunbound/libworker.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/libunbound/libworker.c,v > diff -u -p -r1.37 libworker.c > --- libunbound/libworker.c 27 Jul 2026 14:14:39 -0000 1.37 > +++ libunbound/libworker.c 20 Sep 2026 09:50:48 -0000 > @@ -651,7 +651,8 @@ int libworker_fg(struct ub_ctx* ctx, str > } > /* process new query */ > if(!mesh_new_callback(w->env->mesh, &qinfo, qflags, &edns, > - w->back->udp_buff, qid, libworker_fg_done_cb, q, 0)) { > + w->back->udp_buff, qid, libworker_fg_done_cb, q, 0, > + &q->unique_info)) { > free(qinfo.qname); > return UB_NOMEM; > } > @@ -732,7 +733,8 @@ int libworker_attach_mesh(struct ub_ctx* > if(async_id) > *async_id = q->querynum; > if(!mesh_new_callback(w->env->mesh, &qinfo, qflags, &edns, > - w->back->udp_buff, qid, libworker_event_done_cb, q, 0)) { > + w->back->udp_buff, qid, libworker_event_done_cb, q, 0, > + &q->unique_info)) { > free(qinfo.qname); > return UB_NOMEM; > } > @@ -870,7 +872,8 @@ handle_newq(struct libworker* w, uint8_t > q->w = w; > /* process new query */ > if(!mesh_new_callback(w->env->mesh, &qinfo, qflags, &edns, > - w->back->udp_buff, qid, libworker_bg_done_cb, q, 0)) { > + w->back->udp_buff, qid, libworker_bg_done_cb, q, 0, > + &q->unique_info)) { > add_bg_result(w, q, NULL, UB_NOMEM, NULL, 0); > } > free(qinfo.qname); > @@ -888,7 +891,8 @@ struct outbound_entry* libworker_send_qu > int check_ratelimit, > struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, > size_t zonelen, int tcp_upstream, int ssl_upstream, char* > tls_auth_name, > - struct module_qstate* q, int* was_ratelimited) > + struct module_qstate* q, int* was_ratelimited, > + int* ratelimit_incremented) > { > struct libworker* w = (struct libworker*)q->env->worker; > struct outbound_entry* e = (struct outbound_entry*)regional_alloc( > @@ -900,7 +904,7 @@ struct outbound_entry* libworker_send_qu > want_dnssec, nocaps, check_ratelimit, tcp_upstream, > ssl_upstream, > tls_auth_name, addr, addrlen, zone, zonelen, q, > libworker_handle_service_reply, e, w->back->udp_buff, > q->env, > - was_ratelimited); > + was_ratelimited, ratelimit_incremented); > if(!e->qsent) { > return NULL; > } > @@ -985,7 +989,8 @@ struct outbound_entry* worker_send_query > struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t > ATTR_UNUSED(addrlen), > uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), int > ATTR_UNUSED(tcp_upstream), > int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), > - struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited)) > + struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited), > + int* ATTR_UNUSED(ratelimit_incremented)) > { > log_assert(0); > return 0; > Index: libunbound/remote.h > =================================================================== > RCS file: libunbound/remote.h > diff -N libunbound/remote.h > --- /dev/null 1 Jan 1970 00:00:00 -0000 > +++ libunbound/remote.h 20 Sep 2026 09:50:48 -0000 > @@ -0,0 +1,65 @@ > +/* > + * libunbound/remote.h - prototypes for remote control methods. > + * > + * Copyright (c) 2026, NLnet Labs. All rights reserved. > + * > + * This software is open source. > + * > + * Redistribution and use in source and binary forms, with or without > + * modification, are permitted provided that the following conditions > + * are met: > + * > + * Redistributions of source code must retain the above copyright notice, > + * this list of conditions and the following disclaimer. > + * > + * Redistributions in binary form must reproduce the above copyright > notice, > + * this list of conditions and the following disclaimer in the > documentation > + * and/or other materials provided with the distribution. > + * > + * Neither the name of the NLNET LABS nor the names of its contributors > may > + * be used to endorse or promote products derived from this software > without > + * specific prior written permission. > + * > + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS > + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT > + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR > + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT > + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, > + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT > LIMITED > + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR > + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF > + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING > + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS > + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. > + */ > + > +/** > + * \file > + * > + * This file declares the methods that must be implemented to use the > + * remote control service. > + */ > + > +#ifndef LIBUNBOUND_REMOTE_H > +#define LIBUNBOUND_REMOTE_H > + > +struct comm_reply; > +struct comm_point; > + > +/** fast reload thread commands to remote service thread event callback */ > +void fast_reload_service_cb(int fd, short bits, void* arg); > + > +/** fast reload callback for the remote control client connection */ > +int fast_reload_client_callback(struct comm_point* c, void* arg, int err, > + struct comm_reply* rep); > + > +/** handle remote control accept callbacks */ > +int remote_accept_callback(struct comm_point*, void*, int, struct > comm_reply*); > + > +/** handle remote control data callbacks */ > +int remote_control_callback(struct comm_point*, void*, int, struct > comm_reply*); > + > +/** routine to printout option values over SSL */ > +void remote_get_opt_ssl(char* line, void* arg); > + > +#endif /* LIBUNBOUND_REMOTE_H */ > Index: libunbound/worker.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/libunbound/worker.h,v > diff -u -p -r1.7 worker.h > --- libunbound/worker.h 23 Feb 2022 12:04:05 -0000 1.7 > +++ libunbound/worker.h 20 Sep 2026 09:50:48 -0000 > @@ -70,6 +70,8 @@ struct query_info; > * @param q: which query state to reactivate upon return. > * @param was_ratelimited: it will signal back if the query failed to > pass the > * ratelimit check. > + * @param ratelimit_incremented: set to true if the ratelimit counter > + * was increased. > * @return: false on failure (memory or socket related). no query was > * sent. > */ > @@ -78,7 +80,8 @@ struct outbound_entry* libworker_send_qu > int check_ratelimit, > struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, > size_t zonelen, int tcp_upstream, int ssl_upstream, char* > tls_auth_name, > - struct module_qstate* q, int* was_ratelimited); > + struct module_qstate* q, int* was_ratelimited, > + int* ratelimit_incremented); > > /** process incoming serviced query replies from the network */ > int libworker_handle_service_reply(struct comm_point* c, void* arg, int > error, > @@ -126,6 +129,8 @@ void worker_sighandler(int sig, void* ar > * @param q: which query state to reactivate upon return. > * @param was_ratelimited: it will signal back if the query failed to > pass the > * ratelimit check. > + * @param ratelimit_incremented: set to true if the ratelimit counter > + * was increased. > * @return: false on failure (memory or socket related). no query was > * sent. > */ > @@ -134,7 +139,8 @@ struct outbound_entry* worker_send_query > int check_ratelimit, > struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, > size_t zonelen, int tcp_upstream, int ssl_upstream, char* > tls_auth_name, > - struct module_qstate* q, int* was_ratelimited); > + struct module_qstate* q, int* was_ratelimited, > + int* ratelimit_incremented); > > /** > * process control messages from the main thread. Frees the control > @@ -170,14 +176,5 @@ void worker_start_accept(void* arg); > > /** stop accept callback handler */ > void worker_stop_accept(void* arg); > - > -/** handle remote control accept callbacks */ > -int remote_accept_callback(struct comm_point*, void*, int, struct > comm_reply*); > - > -/** handle remote control data callbacks */ > -int remote_control_callback(struct comm_point*, void*, int, struct > comm_reply*); > - > -/** routine to printout option values over SSL */ > -void remote_get_opt_ssl(char* line, void* arg); > > #endif /* LIBUNBOUND_WORKER_H */ > Index: respip/respip.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/respip/respip.c,v > diff -u -p -r1.19 respip.c > --- respip/respip.c 27 Jul 2026 14:14:39 -0000 1.19 > +++ respip/respip.c 20 Sep 2026 09:50:48 -0000 > @@ -899,27 +899,34 @@ respip_rewrite_reply(const struct query_ > int rpz_cname_override = 0; > char* log_name = NULL; > > - if(!cinfo) > - goto done; > - ctaglist = cinfo->taglist; > - ctaglen = cinfo->taglen; > - tag_actions = cinfo->tag_actions; > - tag_actions_size = cinfo->tag_actions_size; > - tag_datas = cinfo->tag_datas; > - tag_datas_size = cinfo->tag_datas_size; > - if(cinfo->view) { > - view = cinfo->view; > - lock_rw_rdlock(&view->lock); > - } else if(cinfo->view_name) { > - view = views_find_view(views, cinfo->view_name, 0); > - if(!view) { > - /* If the view no longer exists, the rewrite can > not > - * be processed further. */ > - verbose(VERB_ALGO, "respip: failed because view %s > no " > - "longer exists", cinfo->view_name); > - return 0; > + if(!cinfo) { > + /* Internal mesh sub-query (e.g. dns64 A lookup): no > + * per-client view/tags, but global response-ip and RPZ > + * rpz-ip must still apply. */ > + ctaglist = NULL; ctaglen = 0; > + tag_actions = NULL; tag_actions_size = 0; > + tag_datas = NULL; tag_datas_size = 0; > + } else { > + ctaglist = cinfo->taglist; > + ctaglen = cinfo->taglen; > + tag_actions = cinfo->tag_actions; > + tag_actions_size = cinfo->tag_actions_size; > + tag_datas = cinfo->tag_datas; > + tag_datas_size = cinfo->tag_datas_size; > + if(cinfo->view) { > + view = cinfo->view; > + lock_rw_rdlock(&view->lock); > + } else if(cinfo->view_name) { > + view = views_find_view(views, cinfo->view_name, 0); > + if(!view) { > + /* If the view no longer exists, the > rewrite can not > + * be processed further. */ > + verbose(VERB_ALGO, "respip: failed because > view %s no " > + "longer exists", cinfo->view_name); > + return 0; > + } > + /* The view is rdlocked by views_find_view. */ > } > - /* The view is rdlocked by views_find_view. */ > } > > log_assert(ipset); > @@ -1157,8 +1164,10 @@ respip_operate(struct module_qstate* qst > * 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; > @@ -1172,6 +1181,7 @@ respip_operate(struct module_qstate* qst > servfail: > qstate->return_rcode = LDNS_RCODE_SERVFAIL; > qstate->return_msg = NULL; > + qstate->ext_state[id] = module_finished; > } > > int > @@ -1268,6 +1278,7 @@ respip_inform_super(struct module_qstate > return; > > fail: > + errinf(super, "CNAME lookup failed"); > super->return_rcode = LDNS_RCODE_SERVFAIL; > super->return_msg = NULL; > return; > Index: services/authzone.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/authzone.c,v > diff -u -p -r1.33 authzone.c > --- services/authzone.c 27 Jul 2026 14:14:39 -0000 1.33 > +++ services/authzone.c 20 Sep 2026 09:50:48 -0000 > @@ -55,6 +55,7 @@ > #include "util/log.h" > #include "util/module.h" > #include "util/random.h" > +#include "util/timeval_func.h" > #include "services/cache/dns.h" > #include "services/outside_network.h" > #include "services/listen_dnsport.h" > @@ -95,6 +96,8 @@ > /** number of timeouts before we fallback from IXFR to AXFR, > * because some versions of servers (eg. dnsmasq) drop IXFR packets. */ > #define NUM_TIMEOUTS_FALLBACK_IXFR 3 > +/** number of IXFRs before an AXFR is performed, to consolidate RPZ > memory. */ > +#define NUM_IXFR_BEFORE_AXFR 5 > > /** pick up nextprobe task to start waiting to perform transfer actions */ > static void xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env, > @@ -106,6 +109,9 @@ static void xfr_probe_send_or_end(struct > * or transfer task if nothing to probe, or false if already in progress > */ > static int xfr_start_probe(struct auth_xfer* xfr, struct module_env* env, > struct auth_master* spec); > +/** copy the master addresses from the task_probe lookups to the > allow_notify > + * list of masters */ > +static void probe_copy_masters_for_allow_notify(struct auth_xfer* xfr); > /** delete xfer structure (not its tree entry) */ > void auth_xfer_delete(struct auth_xfer* xfr); > > @@ -386,6 +392,20 @@ auth_data_del(rbnode_type* n, void* ATTR > auth_data_delete(z); > } > > +/** delete chunklist */ > +static void > +auth_chunk_list_delete(struct auth_chunk* first) > +{ > + struct auth_chunk* c, *cn; > + c = first; > + while(c) { > + cn = c->next; > + free(c->data); > + free(c); > + c = cn; > + } > +} > + > /** delete an auth zone structure (tree remove must be done elsewhere) */ > static void > auth_zone_delete(struct auth_zone* z, struct auth_zones* az) > @@ -407,6 +427,7 @@ auth_zone_delete(struct auth_zone* z, st > } > if(z->rpz) > rpz_delete(z->rpz); > + auth_chunk_list_delete(z->perform_write_chunk_list); > free(z->name); > free(z->zonefile); > free(z); > @@ -432,7 +453,12 @@ auth_zone_create(struct auth_zones* az, > 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 */ > @@ -1175,6 +1201,22 @@ az_insert_rr(struct auth_zone* z, uint8_ > log_err("wrong class for RR"); > return 0; > } > + if(rr_type == LDNS_RR_TYPE_A && rdatalen != 6 /* 2 + 4 */) { > + log_err("malformed A record"); > + return 0; > + } else if(rr_type == LDNS_RR_TYPE_AAAA && rdatalen != 18 /* 2 + 16 > */) { > + log_err("malformed AAAA record"); > + return 0; > + } > + if(!dname_subdomain_c(dname, z->name)) { > + char nm[LDNS_MAX_DOMAINLEN], zn[LDNS_MAX_DOMAINLEN]; > + dname_str(dname, nm); > + dname_str(z->name, zn); > + verbose(VERB_ALGO, "auth-zone %s: dropping out-of-zone RR " > + "%s", zn, nm); > + if(duplicate) *duplicate=1; /* treat as bad insert */ > + return 1; > + } > if(!(node=az_domain_find_or_create(z, dname, dname_len))) { > log_err("cannot create domain"); > return 0; > @@ -1182,6 +1224,10 @@ az_insert_rr(struct auth_zone* z, uint8_ > if(!az_domain_add_rr(node, rr_type, rr_ttl, rdata, rdatalen, > duplicate)) { > log_err("cannot add RR to domain"); > + if(node->rrsets == NULL) { > + (void)rbtree_delete(&z->data, node); > + auth_data_delete(node); > + } > return 0; > } > if(z->rpz) { > @@ -1505,6 +1551,11 @@ az_parse_file(struct auth_zone* z, FILE* > "exceeded", fname, > state->lineno); > return 0; > } > + /* A $INCLUDE is not expected for a > secondary zone. */ > + if(z->zone_is_slave) { > + log_err("%s:%d $INCLUDE not > allowed for secondary zone", fname, state->lineno); > + return 0; > + } > /* skip spaces */ > while(*incfile == ' ' || *incfile == '\t') > incfile++; > @@ -1570,6 +1621,16 @@ az_parse_file(struct auth_zone* z, FILE* > return 1; > } > > +void auth_zone_clear_data(struct auth_zone* z) > +{ > + /* clear the data tree */ > + traverse_postorder(&z->data, auth_data_del, NULL); > + rbtree_init(&z->data, &auth_data_cmp); > + /* clear the RPZ policies */ > + if(z->rpz) > + rpz_clear(z->rpz); > +} > + > int > auth_zone_read_zonefile(struct auth_zone* z, struct config_file* cfg) > { > @@ -1592,10 +1653,16 @@ auth_zone_read_zonefile(struct auth_zone > in = fopen(zfilename, "r"); > if(!in) { > char* n = sldns_wire2str_dname(z->name, z->namelen); > - if(z->zone_is_slave && errno == ENOENT) { > - /* we fetch the zone contents later, no file yet */ > - verbose(VERB_ALGO, "no zonefile %s for %s", > - zfilename, n?n:"error"); > + if(errno == ENOENT) { > + /* For a secondary, fetch the zone contents later, > no > + * file yet. For a primary, no way to fetch the > zone, > + * so warn. */ > + if(z->zone_is_slave) > + verbose(VERB_ALGO, "no zonefile %s for %s", > + zfilename, n?n:"error"); > + else > + log_warn("no zonefile %s for %s", > + zfilename, n?n:"error"); > free(n); > return 1; > } > @@ -1798,9 +1865,11 @@ auth_zones_read_zones(struct auth_zones* > RBTREE_FOR(z, struct auth_zone*, &az->ztree) { > lock_rw_wrlock(&z->lock); > if(!auth_zone_read_zonefile(z, cfg)) { > + /* For both secondary and primary zones, not fatal. > + * This keeps the server up. */ > + auth_zone_clear_data(z); > lock_rw_unlock(&z->lock); > - lock_rw_unlock(&az->lock); > - return 0; > + continue; > } > if(z->zonefile && z->zonefile[0]!=0 && env) > zonemd_offline_verify(z, env, mods); > @@ -2076,6 +2145,7 @@ auth_xfer_setup(struct auth_zone* z, str > if(!xfr_find_soa(z, x)) { > return 1; > } > + x->is_rpz = (z->rpz!=NULL); > /* nothing for probe, nextprobe and transfer tasks */ > return 1; > } > @@ -2135,6 +2205,9 @@ auth_zones_cfg(struct auth_zones* az, st > } > return 0; > } > + /* Populate the xfer related options early since we may create one > now */ > + z->max_transfer_size = c->max_transfer_size; > + z->max_transfer_time = c->max_transfer_time; > if(c->masters || c->urls) { > if(!(x=auth_zones_find_or_add_xfer(az, z))) { > lock_rw_unlock(&az->lock); > @@ -2168,7 +2241,12 @@ auth_zones_cfg(struct auth_zones* az, st > z->zonemd_reject_absence = c->zonemd_reject_absence; > if(c->isrpz && !z->rpz){ > if(!(z->rpz = rpz_create(c))){ > - fatal_exit("Could not setup RPZ zones"); > + log_err("Could not setup RPZ zones"); > + if(x) { > + lock_basic_unlock(&x->lock); > + } > + lock_rw_unlock(&z->lock); > + lock_rw_unlock(&az->rpz_lock); > return 0; > } > lock_protect(&z->lock, &z->rpz->local_zones, > sizeof(*z->rpz)); > @@ -2206,6 +2284,10 @@ auth_zones_cfg(struct auth_zones* az, st > lock_rw_unlock(&z->lock); > return 0; > } > + /* Pick up allow notify entries, early. This works for > + * addresses and netblocks. */ > + if(!x->allow_notify_list) > + probe_copy_masters_for_allow_notify(x); > lock_basic_unlock(&x->lock); > } > > @@ -2302,17 +2384,11 @@ static void > auth_chunks_delete(struct auth_transfer* at) > { > if(at->chunks_first) { > - struct auth_chunk* c, *cn; > - c = at->chunks_first; > - while(c) { > - cn = c->next; > - free(c->data); > - free(c); > - c = cn; > - } > + auth_chunk_list_delete(at->chunks_first); > } > at->chunks_first = NULL; > at->chunks_last = NULL; > + at->chunks_total = 0; > } > > /** free master addr list */ > @@ -2644,7 +2720,7 @@ az_empty_nonterminal(struct auth_zone* z > while(next && (rbnode_type*)next != RBTREE_NULL && next->rrsets == > NULL) { > /* the next name has empty rrsets, is an empty nonterminal > * itself, see if there exists something below it */ > - next = (struct auth_data*)rbtree_next(&node->node); > + next = (struct auth_data*)rbtree_next(&next->node); > } > if((rbnode_type*)next == RBTREE_NULL || !next) { > /* there is no next node, so something below it cannot > @@ -3525,7 +3601,13 @@ int auth_zones_lookup(struct auth_zones* > *fallback = 1; > return 0; > } > - if(z->zone_expired) { > + if(z->zone_expired || (z->zonemd_check && z->zonemd_callback_env)) > { > + /* Do not serve from a zonemd-check zone while its ZONEMD > + * verification is still pending: the content is not yet > known > + * to pass the configured check. The pending marker > + * (zonemd_callback_env) is set under z->lock when the > async > + * lookup is spawned and cleared by the callback under > z->lock, > + * so this test is race-free. */ > *fallback = z->fallback_enabled; > lock_rw_unlock(&z->lock); > return 0; > @@ -3627,7 +3709,10 @@ int auth_zones_downstream_answer(struct > lock_rw_unlock(&z->lock); > return 0; > } > - if(z->zone_expired) { > + if(z->zone_expired || (z->zonemd_check && z->zonemd_callback_env)) > { > + /* see auth_zones_lookup: a pending ZONEMD verification is > + * treated like expiry - the zone content is not yet known > + * to pass the configured check. */ > if(z->fallback_enabled) { > lock_rw_unlock(&z->lock); > return 0; > @@ -4298,7 +4383,7 @@ xfr_create_ixfr_packet(struct auth_xfer* > { > struct query_info qinfo; > uint32_t serial; > - int have_zone; > + int have_zone, get_full = 0; > have_zone = xfr->have_zone; > serial = xfr->serial; > > @@ -4311,7 +4396,18 @@ xfr_create_ixfr_packet(struct auth_xfer* > xfr->task_transfer->on_ixfr_is_axfr = 0; > xfr->task_transfer->on_ixfr = 1; > qinfo.qtype = LDNS_RR_TYPE_IXFR; > - if(!have_zone || xfr->task_transfer->ixfr_fail || !master->ixfr) { > + if(xfr->num_ixfrs >= NUM_IXFR_BEFORE_AXFR && xfr->is_rpz) { > + /* For the RPZ, an IXFR is going to grow regions, and a > + * full transfer, zonefile read, AXFR and HTTP clear the > + * region, but IXFR does not. That memory keeps growing, > + * and getting a full transfer with AXFR here resets that. > + * The rpz->client_set->region, rpz->ns_set->region and > + * rpz->respip_set->region need to be reset, they are for > + * rpz-client-ip, rpz-nsip and rpz-ip. */ > + get_full = 1; > + } > + if(!have_zone || xfr->task_transfer->ixfr_fail || !master->ixfr > + || get_full) { > qinfo.qtype = LDNS_RR_TYPE_AXFR; > xfr->task_transfer->ixfr_fail = 0; > xfr->task_transfer->on_ixfr = 0; > @@ -4462,29 +4558,31 @@ chunkline_get_line(struct auth_chunk** c > } > > /** count number of open and closed parenthesis in a chunkline */ > -static int > +int > chunkline_count_parens(sldns_buffer* buf, size_t start) > { > size_t end = sldns_buffer_position(buf); > size_t i; > int count = 0; > - int squote = 0, dquote = 0; > + int dquote = 0; > + char prev_c = 0; > for(i=start; i char c = (char)sldns_buffer_read_u8_at(buf, i); > - if(squote && c != '\'') continue; > - if(dquote && c != '"') continue; > - if(c == '"') > + if(dquote && !(c == '"' && prev_c != '\\')) { > + prev_c = (prev_c == '\\' && c == '\\') ? 0 : c; > + continue; > + } > + if(c == '"' && prev_c != '\\') > dquote = !dquote; /* skip quoted part */ > - else if(c == '\'') > - squote = !squote; /* skip quoted part */ > - else if(c == '(') > + else if(c == '(' && prev_c != '\\') > count ++; > - else if(c == ')') > + else if(c == ')' && prev_c != '\\') > count --; > - else if(c == ';') { > + else if(c == ';' && prev_c != '\\') { > /* rest is a comment */ > return count; > } > + prev_c = (prev_c == '\\' && c == '\\') ? 0 : c; > } > return count; > } > @@ -4495,20 +4593,22 @@ chunkline_remove_trailcomment(sldns_buff > { > size_t end = sldns_buffer_position(buf); > size_t i; > - int squote = 0, dquote = 0; > + int dquote = 0; > + char prev_c = 0; > for(i=start; i char c = (char)sldns_buffer_read_u8_at(buf, i); > - if(squote && c != '\'') continue; > - if(dquote && c != '"') continue; > - if(c == '"') > + if(dquote && !(c == '"' && prev_c != '\\')) { > + prev_c = (prev_c == '\\' && c == '\\') ? 0 : c; > + continue; > + } > + if(c == '"' && prev_c != '\\') > dquote = !dquote; /* skip quoted part */ > - else if(c == '\'') > - squote = !squote; /* skip quoted part */ > - else if(c == ';') { > + else if(c == ';' && prev_c != '\\') { > /* rest is a comment */ > sldns_buffer_set_position(buf, i); > return; > } > + prev_c = (prev_c == '\\' && c == '\\') ? 0 : c; > } > /* nothing to remove */ > } > @@ -4932,6 +5032,8 @@ apply_ixfr(struct auth_xfer* xfr, struct > int delmode = 0; > int softfail = 0; > > + xfr->num_ixfrs++; > + > /* start RR iterator over chunklist of packets */ > chunk_rrlist_start(xfr, &rr_chunk, &rr_num, &rr_pos); > while(!chunk_rrlist_end(rr_chunk, rr_num)) { > @@ -5067,16 +5169,11 @@ apply_axfr(struct auth_xfer* xfr, struct > size_t rr_counter = 0; > int have_end_soa = 0; > > - /* clear the data tree */ > - traverse_postorder(&z->data, auth_data_del, NULL); > - rbtree_init(&z->data, &auth_data_cmp); > - /* clear the RPZ policies */ > - if(z->rpz) > - rpz_clear(z->rpz); > - > + auth_zone_clear_data(z); > xfr->have_zone = 0; > xfr->serial = 0; > xfr->soa_zone_acquired = 0; > + xfr->num_ixfrs = 0; > > /* insert all RRs in to the zone */ > /* insert the SOA only once, skip the last one */ > @@ -5169,16 +5266,11 @@ apply_http(struct auth_xfer* xfr, struct > return 0; > } > > - /* clear the data tree */ > - traverse_postorder(&z->data, auth_data_del, NULL); > - rbtree_init(&z->data, &auth_data_cmp); > - /* clear the RPZ policies */ > - if(z->rpz) > - rpz_clear(z->rpz); > - > + auth_zone_clear_data(z); > xfr->have_zone = 0; > xfr->serial = 0; > xfr->soa_zone_acquired = 0; > + xfr->num_ixfrs = 0; > > chunk = xfr->task_transfer->chunks_first; > chunk_pos = 0; > @@ -5224,7 +5316,7 @@ apply_http(struct auth_xfer* xfr, struct > > /** write http chunks to zonefile to create downloaded file */ > static int > -auth_zone_write_chunks(struct auth_xfer* xfr, const char* fname) > +auth_zone_write_chunks(struct auth_chunk* chunk_list, const char* fname) > { > FILE* out; > struct auth_chunk* p; > @@ -5233,7 +5325,7 @@ auth_zone_write_chunks(struct auth_xfer* > log_err("could not open %s: %s", fname, strerror(errno)); > return 0; > } > - for(p = xfr->task_transfer->chunks_first; p ; p = p->next) { > + for(p = chunk_list; p ; p = p->next) { > if(!write_out(out, (char*)p->data, p->len)) { > log_err("could not write http download to %s", > fname); > fclose(out); > @@ -5244,34 +5336,18 @@ auth_zone_write_chunks(struct auth_xfer* > return 1; > } > > -/** write to zonefile after zone has been updated */ > +/** write to zonefile after zone has been updated, z has rdlock by > caller. */ > static void > -xfr_write_after_update(struct auth_xfer* xfr, struct module_env* env) > +zone_write_after_update(struct auth_zone* z, struct module_env* env, > + struct auth_chunk* chunk_list) > { > struct config_file* cfg = env->cfg; > - struct auth_zone* z; > char tmpfile[1024]; > char* zfilename; > - lock_basic_unlock(&xfr->lock); > - > - /* get lock again, so it is a readlock and concurrently queries > - * can be answered */ > - lock_rw_rdlock(&env->auth_zones->lock); > - z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, > - xfr->dclass); > - if(!z) { > - lock_rw_unlock(&env->auth_zones->lock); > - /* the zone is gone, ignore xfr results */ > - lock_basic_lock(&xfr->lock); > - return; > - } > - lock_rw_rdlock(&z->lock); > - lock_basic_lock(&xfr->lock); > - lock_rw_unlock(&env->auth_zones->lock); > > if(z->zonefile == NULL || z->zonefile[0] == 0) { > - lock_rw_unlock(&z->lock); > /* no write needed, no zonefile set */ > + auth_chunk_list_delete(chunk_list); > return; > } > zfilename = z->zonefile; > @@ -5288,21 +5364,21 @@ xfr_write_after_update(struct auth_xfer* > if((size_t)strlen(zfilename) + 16 > sizeof(tmpfile)) { > verbose(VERB_ALGO, "tmpfilename too long, cannot update " > " zonefile %s", zfilename); > - lock_rw_unlock(&z->lock); > + auth_chunk_list_delete(chunk_list); > return; > } > snprintf(tmpfile, sizeof(tmpfile), "%s.tmp%u", zfilename, > (unsigned)getpid()); > - if(xfr->task_transfer->master->http) { > + if(chunk_list) { > /* use the stored chunk list to write them */ > - if(!auth_zone_write_chunks(xfr, tmpfile)) { > + if(!auth_zone_write_chunks(chunk_list, tmpfile)) { > unlink(tmpfile); > - lock_rw_unlock(&z->lock); > + auth_chunk_list_delete(chunk_list); > return; > } > + auth_chunk_list_delete(chunk_list); > } else if(!auth_zone_write_file(z, tmpfile)) { > unlink(tmpfile); > - lock_rw_unlock(&z->lock); > return; > } > #ifdef UB_ON_WINDOWS > @@ -5312,9 +5388,57 @@ xfr_write_after_update(struct auth_xfer* > log_err("could not rename(%s, %s): %s", tmpfile, zfilename, > strerror(errno)); > unlink(tmpfile); > - lock_rw_unlock(&z->lock); > return; > } > +} > + > +/** write to zonefile after zone has updated, reacquires z readlock. */ > +static void > +zone_write_after_update_reacq(uint8_t* bakname, size_t baknamelen, > + uint16_t bakdclass, struct module_env* env, > + struct auth_chunk* chunk_list) > +{ > + struct auth_zone* z; > + /* get lock again, so it is a readlock and concurrently queries > + * can be answered */ > + lock_rw_rdlock(&env->auth_zones->lock); > + z = auth_zone_find(env->auth_zones, bakname, baknamelen, > bakdclass); > + if(!z) { > + lock_rw_unlock(&env->auth_zones->lock); > + /* the zone is gone, ignore xfr results */ > + return; > + } > + lock_rw_rdlock(&z->lock); > + lock_rw_unlock(&env->auth_zones->lock); > + > + zone_write_after_update(z, env, chunk_list); > + lock_rw_unlock(&z->lock); > +} > + > +/** write to zonefile after zone has been updated */ > +static void > +xfr_write_after_update(struct auth_xfer* xfr, struct module_env* env, > + struct auth_chunk* chunk_list) > +{ > + struct auth_zone* z; > + lock_basic_unlock(&xfr->lock); > + > + /* get lock again, so it is a readlock and concurrently queries > + * can be answered */ > + lock_rw_rdlock(&env->auth_zones->lock); > + z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, > + xfr->dclass); > + if(!z) { > + lock_rw_unlock(&env->auth_zones->lock); > + /* the zone is gone, ignore xfr results */ > + lock_basic_lock(&xfr->lock); > + return; > + } > + lock_rw_rdlock(&z->lock); > + lock_basic_lock(&xfr->lock); > + lock_rw_unlock(&env->auth_zones->lock); > + > + zone_write_after_update(z, env, chunk_list); > lock_rw_unlock(&z->lock); > } > > @@ -5347,6 +5471,8 @@ xfr_process_chunk_list(struct auth_xfer* > int* ixfr_fail) > { > struct auth_zone* z; > + int zonemd_in_progress; > + struct auth_chunk* current_chunk_list = NULL; > > /* obtain locks and structures */ > lock_basic_unlock(&xfr->lock); > @@ -5359,6 +5485,7 @@ xfr_process_chunk_list(struct auth_xfer* > /* apply data */ > if(xfr->task_transfer->master->http) { > if(!apply_http(xfr, z, env->scratch_buffer)) { > + auth_zone_clear_data(z); > lock_rw_unlock(&z->lock); > verbose(VERB_ALGO, "http from %s: could not store > data", > xfr->task_transfer->master->host); > @@ -5367,6 +5494,7 @@ xfr_process_chunk_list(struct auth_xfer* > } else if(xfr->task_transfer->on_ixfr && > !xfr->task_transfer->on_ixfr_is_axfr) { > if(!apply_ixfr(xfr, z, env->scratch_buffer)) { > + auth_zone_clear_data(z); > lock_rw_unlock(&z->lock); > verbose(VERB_ALGO, "xfr from %s: could not store > IXFR" > " data", xfr->task_transfer->master->host); > @@ -5375,6 +5503,7 @@ xfr_process_chunk_list(struct auth_xfer* > } > } else { > if(!apply_axfr(xfr, z, env->scratch_buffer)) { > + auth_zone_clear_data(z); > lock_rw_unlock(&z->lock); > verbose(VERB_ALGO, "xfr from %s: could not store > AXFR" > " data", xfr->task_transfer->master->host); > @@ -5391,6 +5520,7 @@ xfr_process_chunk_list(struct auth_xfer* > } > z->soa_zone_acquired = *env->now; > xfr->soa_zone_acquired = *env->now; > + xfr->is_rpz = (z->rpz!=NULL); > > /* release xfr lock while verifying zonemd because it may have > * to spawn lookups in the state machines */ > @@ -5426,6 +5556,25 @@ xfr_process_chunk_list(struct auth_xfer* > if(z->rpz) > rpz_finish_config(z->rpz); > > + if(z->zonemd_check && z->zonemd_callback_env) { > + zonemd_in_progress = 1; > + z->zonemd_callback_perform_write = 1; > + auth_chunk_list_delete(z->perform_write_chunk_list); > + z->perform_write_chunk_list = NULL; > + if(xfr->task_transfer->master->http) { > + z->perform_write_chunk_list = > xfr->task_transfer->chunks_first; > + xfr->task_transfer->chunks_first = NULL; > + auth_chunks_delete(xfr->task_transfer); > + } > + } else { > + zonemd_in_progress = 0; > + z->zonemd_callback_perform_write = 0; > + if(xfr->task_transfer->master->http) { > + current_chunk_list = > xfr->task_transfer->chunks_first; > + xfr->task_transfer->chunks_first = NULL; > + auth_chunks_delete(xfr->task_transfer); > + } > + } > /* unlock */ > lock_rw_unlock(&z->lock); > > @@ -5436,20 +5585,56 @@ xfr_process_chunk_list(struct auth_xfer* > (unsigned)xfr->serial); > } > /* see if we need to write to a zonefile */ > - xfr_write_after_update(xfr, env); > + if(!zonemd_in_progress) { > + xfr_write_after_update(xfr, env, current_chunk_list); > + } > return 1; > } > > +/** Stop lookup using callback */ > +static void > +xfr_stop_lookup(struct auth_master** lookup_target, void* > lookup_unique_info, > + int lookup_aaaa, uint16_t dclass, struct mesh_area* mesh, > + mesh_cb_func_type cb, void* cb_arg) > +{ > + struct query_info qinfo; > + uint8_t dname[LDNS_MAX_DOMAINLEN+1]; > + if(!*lookup_target) return; > + qinfo.qname_len = sizeof(dname); > + if(sldns_str2wire_dname_buf((*lookup_target)->host, dname, > + &qinfo.qname_len) != 0) { > + *lookup_target = NULL; > + return; > + } > + qinfo.qname = dname; > + qinfo.qclass = dclass; > + qinfo.qtype = lookup_aaaa ? LDNS_RR_TYPE_AAAA : LDNS_RR_TYPE_A; > + qinfo.local_alias = NULL; > + log_query_info(VERB_ALGO, "removing xfr callback", &qinfo); > + > + mesh_remove_callback(mesh, &qinfo, BIT_RD, cb, cb_arg, > + lookup_unique_info); > + *lookup_target = NULL; > +} > + > /** disown task_transfer. caller must hold xfr.lock */ > static void > xfr_transfer_disown(struct auth_xfer* xfr) > { > + /* remove data chunks */ > + auth_chunks_delete(xfr->task_transfer); > /* remove timer (from this worker's event base) */ > comm_timer_delete(xfr->task_transfer->timer); > xfr->task_transfer->timer = NULL; > /* remove the commpoint */ > comm_point_delete(xfr->task_transfer->cp); > xfr->task_transfer->cp = NULL; > + if(xfr->task_transfer->env) > + xfr_stop_lookup(&xfr->task_transfer->lookup_target, > + xfr->task_transfer->lookup_unique_info, > + xfr->task_transfer->lookup_aaaa, xfr->dclass, > + xfr->task_transfer->env->mesh, > + &auth_xfer_transfer_lookup_callback, xfr); > /* we don't own this item anymore */ > xfr->task_transfer->worker = NULL; > xfr->task_transfer->env = NULL; > @@ -5516,7 +5701,8 @@ xfr_transfer_lookup_host(struct auth_xfe > * called straight away */ > lock_basic_unlock(&xfr->lock); > if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, > - &auth_xfer_transfer_lookup_callback, xfr, 0)) { > + &auth_xfer_transfer_lookup_callback, xfr, 0, > + &xfr->task_transfer->lookup_unique_info)) { > lock_basic_lock(&xfr->lock); > log_err("out of memory lookup up master %s", master->host); > return 0; > @@ -5574,6 +5760,7 @@ xfr_transfer_init_fetch(struct auth_xfer > t.tv_sec = timeout/1000; > t.tv_usec = (timeout%1000)*1000; > #endif > + xfr->task_transfer->start_time = *env->now_tv; > > if(master->http) { > /* perform http fetch */ > @@ -5743,6 +5930,31 @@ xfr_master_add_addrs(struct auth_master* > } > } > > +/** check if the lookup target name equals the found answer name. */ > +static int > +xfer_target_equals_answer_name(struct auth_master* lookup_target, > + struct ub_packed_rrset_key* answer, struct query_info* rq, > + struct reply_info* rep) > +{ > + uint8_t qname[LDNS_MAX_DOMAINLEN+1]; > + size_t qname_len; > + if(!lookup_target) return 0; > + if(!answer) return 0; > + qname_len = sizeof(qname); > + if(sldns_str2wire_dname_buf(lookup_target->host, qname, &qname_len) > + != 0) { > + verbose(VERB_ALGO, "xfer_target_equals_answer_name: could > not parse auth host name"); > + return 0; > + } > + if(query_dname_compare(answer->rk.dname, qname) == 0) > + return 1; > + /* It could be a CNAME. */ > + if(reply_find_rrset_section_an(rep, qname, qname_len, > + LDNS_RR_TYPE_CNAME, rq->qclass)) > + return 1; > + return 0; > +} > + > /** 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 sec, char* why_bogus, int > ATTR_UNUSED(was_ratelimited)) > @@ -5781,21 +5993,29 @@ void auth_xfer_transfer_lookup_callback( > /* parsed successfully */ > struct ub_packed_rrset_key* answer = > reply_find_answer_rrset(&rq, rep); > - if(answer) { > + if(answer && xfer_target_equals_answer_name( > + xfr->task_transfer->lookup_target, answer, > + &rq, rep)) { > xfr_master_add_addrs(xfr->task_transfer-> > lookup_target, answer, > wanted_qtype); > + } else if(answer) { > + if(verbosity >= VERB_ALGO) { > + char zname[LDNS_MAX_DOMAINLEN]; > + dname_str(xfr->name, zname); > + verbose(VERB_ALGO, "auth zone %s > host %s type %s transfer lookup has mismatch in answer name", zname, > ((xfr->task_transfer->lookup_target && > xfr->task_transfer->lookup_target->host) ? > xfr->task_transfer->lookup_target->host : "null"), > (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); > + } > } else { > if(verbosity >= VERB_ALGO) { > char zname[LDNS_MAX_DOMAINLEN]; > dname_str(xfr->name, zname); > - verbose(VERB_ALGO, "auth zone %s > host %s type %s transfer lookup has nodata", zname, > xfr->task_transfer->lookup_target->host, > (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); > + verbose(VERB_ALGO, "auth zone %s > host %s type %s transfer lookup has nodata", zname, > ((xfr->task_transfer->lookup_target && > xfr->task_transfer->lookup_target->host) ? > xfr->task_transfer->lookup_target->host : "null"), > (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); > } > } > } else { > if(verbosity >= VERB_ALGO) { > char zname[LDNS_MAX_DOMAINLEN]; > dname_str(xfr->name, zname); > - verbose(VERB_ALGO, "auth zone %s host %s > type %s transfer lookup has no answer", zname, > xfr->task_transfer->lookup_target->host, > (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); > + verbose(VERB_ALGO, "auth zone %s host %s > type %s transfer lookup has no answer", zname, > ((xfr->task_transfer->lookup_target && > xfr->task_transfer->lookup_target->host) ? > xfr->task_transfer->lookup_target->host : "null"), > (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); > } > } > regional_free_all(temp); > @@ -5803,10 +6023,11 @@ void auth_xfer_transfer_lookup_callback( > if(verbosity >= VERB_ALGO) { > char zname[LDNS_MAX_DOMAINLEN]; > dname_str(xfr->name, zname); > - verbose(VERB_ALGO, "auth zone %s host %s type %s > transfer lookup failed", zname, xfr->task_transfer->lookup_target->host, > (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); > + verbose(VERB_ALGO, "auth zone %s host %s type %s > transfer lookup failed", zname, ((xfr->task_transfer->lookup_target && > xfr->task_transfer->lookup_target->host) ? > xfr->task_transfer->lookup_target->host : "null"), > (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); > } > } > - if(xfr->task_transfer->lookup_target->list && > + if(xfr->task_transfer->lookup_target && > + xfr->task_transfer->lookup_target->list && > xfr->task_transfer->lookup_target == > xfr_transfer_current_master(xfr)) > xfr->task_transfer->scan_addr = > xfr->task_transfer->lookup_target->list; > > @@ -6136,6 +6357,7 @@ xfer_link_data(sldns_buffer* pkt, struct > if(xfr->task_transfer->chunks_last) > xfr->task_transfer->chunks_last->next = e; > xfr->task_transfer->chunks_last = e; > + xfr->task_transfer->chunks_total += e->len; > return 1; > } > > @@ -6231,6 +6453,15 @@ auth_xfer_transfer_timer_callback(void* > xfr_transfer_nexttarget_or_end(xfr, env); > } > > +/** return the time taken by the transfer */ > +static int > +auth_xfer_transfer_time_taken(struct auth_xfer* xfr, struct module_env* > env) > +{ > + struct timeval delta; > + timeval_subtract(&delta, env->now_tv, > &xfr->task_transfer->start_time); > + return ((int)delta.tv_sec)*1000 + ((int)delta.tv_usec)/1000; > +} > + > /** callback for task_transfer tcp connections */ > int > auth_xfer_transfer_tcp_callback(struct comm_point* c, void* arg, int err, > @@ -6297,6 +6528,15 @@ auth_xfer_transfer_tcp_callback(struct c > xfr->task_transfer->master->host); > goto failed; > } > + if(xfr->max_transfer_size > 0 && > + xfr->task_transfer->chunks_total > xfr->max_transfer_size) > { > + char zname[LDNS_MAX_DOMAINLEN]; > + dname_str(xfr->name, zname); > + log_err("auth zone %s transfer from %s exceeded %u bytes, > aborting", > + zname, xfr->task_transfer->master->host, > + (unsigned)xfr->max_transfer_size); > + goto failed; > + } > /* if the transfer is done now, disconnect and process the list */ > if(transferdone) { > comm_point_delete(xfr->task_transfer->cp); > @@ -6305,6 +6545,16 @@ auth_xfer_transfer_tcp_callback(struct c > return 0; > } > > + if(xfr->max_transfer_time > 0 && > + auth_xfer_transfer_time_taken(xfr, env) > > xfr->max_transfer_time) { > + char zname[LDNS_MAX_DOMAINLEN]; > + dname_str(xfr->name, zname); > + log_err("auth zone %s transfer from %s exceeded %u msec > total running time, aborting", > + zname, xfr->task_transfer->master->host, > + (unsigned)xfr->max_transfer_time); > + goto failed; > + } > + > /* if we want to read more messages, setup the commpoint to read > * a DNS packet, and the timeout */ > lock_basic_unlock(&xfr->lock); > @@ -6360,6 +6610,16 @@ auth_xfer_transfer_http_callback(struct > xfr->task_transfer->master->host); > goto failed; > } > + if(xfr->max_transfer_size > 0 && > + xfr->task_transfer->chunks_total > > xfr->max_transfer_size) { > + char zname[LDNS_MAX_DOMAINLEN]; > + dname_str(xfr->name, zname); > + log_err("auth zone %s http %s/%s exceeded %u > bytes, aborting", > + zname, xfr->task_transfer->master->host, > + xfr->task_transfer->master->file, > + (unsigned)xfr->max_transfer_size); > + goto failed; > + } > } > /* if the transfer is done now, disconnect and process the list */ > if(err == NETEVENT_DONE) { > @@ -6371,6 +6631,17 @@ auth_xfer_transfer_http_callback(struct > return 0; > } > > + if(xfr->max_transfer_time > 0 && > + auth_xfer_transfer_time_taken(xfr, env) > > xfr->max_transfer_time) { > + char zname[LDNS_MAX_DOMAINLEN]; > + dname_str(xfr->name, zname); > + log_err("auth zone %s transfer http %s/%s exceeded %u msec > total running time, aborting", > + zname, xfr->task_transfer->master->host, > + xfr->task_transfer->master->file, > + (unsigned)xfr->max_transfer_time); > + goto failed; > + } > + > /* if we want to read more messages, setup the commpoint to read > * a DNS packet, and the timeout */ > lock_basic_unlock(&xfr->lock); > @@ -6413,6 +6684,12 @@ xfr_probe_disown(struct auth_xfer* xfr) > /* remove the commpoint */ > comm_point_delete(xfr->task_probe->cp); > xfr->task_probe->cp = NULL; > + if(xfr->task_probe->env) > + xfr_stop_lookup(&xfr->task_probe->lookup_target, > + xfr->task_probe->lookup_unique_info, > + xfr->task_probe->lookup_aaaa, xfr->dclass, > + xfr->task_probe->env->mesh, > + &auth_xfer_probe_lookup_callback, xfr); > /* we don't own this item anymore */ > xfr->task_probe->worker = NULL; > xfr->task_probe->env = NULL; > @@ -6719,7 +6996,8 @@ xfr_probe_lookup_host(struct auth_xfer* > * called straight away */ > lock_basic_unlock(&xfr->lock); > if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, > - &auth_xfer_probe_lookup_callback, xfr, 0)) { > + &auth_xfer_probe_lookup_callback, xfr, 0, > + &xfr->task_probe->lookup_unique_info)) { > lock_basic_lock(&xfr->lock); > log_err("out of memory lookup up master %s", master->host); > return 0; > @@ -6856,7 +7134,7 @@ void auth_xfer_probe_lookup_callback(voi > 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 */ > @@ -6874,21 +7152,29 @@ void auth_xfer_probe_lookup_callback(voi > /* parsed successfully */ > struct ub_packed_rrset_key* answer = > reply_find_answer_rrset(&rq, rep); > - if(answer) { > + if(answer && xfer_target_equals_answer_name( > + xfr->task_probe->lookup_target, answer, > + &rq, rep)) { > xfr_master_add_addrs(xfr->task_probe-> > lookup_target, answer, > wanted_qtype); > + } else if(answer) { > + if(verbosity >= VERB_ALGO) { > + char zname[LDNS_MAX_DOMAINLEN]; > + dname_str(xfr->name, zname); > + verbose(VERB_ALGO, "auth zone %s > host %s type %s probe lookup has mismatch in answer name", zname, > ((xfr->task_probe->lookup_target && xfr->task_probe->lookup_target->host) ? > xfr->task_probe->lookup_target->host : "null"), > (xfr->task_probe->lookup_aaaa?"AAAA":"A")); > + } > } else { > if(verbosity >= VERB_ALGO) { > char zname[LDNS_MAX_DOMAINLEN]; > dname_str(xfr->name, zname); > - verbose(VERB_ALGO, "auth zone %s > host %s type %s probe lookup has nodata", zname, > xfr->task_probe->lookup_target->host, > (xfr->task_probe->lookup_aaaa?"AAAA":"A")); > + verbose(VERB_ALGO, "auth zone %s > host %s type %s probe lookup has nodata", zname, > ((xfr->task_probe->lookup_target && xfr->task_probe->lookup_target->host) ? > xfr->task_probe->lookup_target->host : "null"), > (xfr->task_probe->lookup_aaaa?"AAAA":"A")); > } > } > } else { > if(verbosity >= VERB_ALGO) { > char zname[LDNS_MAX_DOMAINLEN]; > dname_str(xfr->name, zname); > - verbose(VERB_ALGO, "auth zone %s host %s > type %s probe lookup has no address", zname, > xfr->task_probe->lookup_target->host, > (xfr->task_probe->lookup_aaaa?"AAAA":"A")); > + verbose(VERB_ALGO, "auth zone %s host %s > type %s probe lookup has no address", zname, > ((xfr->task_probe->lookup_target && xfr->task_probe->lookup_target->host) ? > xfr->task_probe->lookup_target->host : "null"), > (xfr->task_probe->lookup_aaaa?"AAAA":"A")); > } > } > regional_free_all(temp); > @@ -6896,10 +7182,11 @@ void auth_xfer_probe_lookup_callback(voi > if(verbosity >= VERB_ALGO) { > char zname[LDNS_MAX_DOMAINLEN]; > dname_str(xfr->name, zname); > - verbose(VERB_ALGO, "auth zone %s host %s type %s > probe lookup failed", zname, xfr->task_probe->lookup_target->host, > (xfr->task_probe->lookup_aaaa?"AAAA":"A")); > + verbose(VERB_ALGO, "auth zone %s host %s type %s > probe lookup failed", zname, ((xfr->task_probe->lookup_target && > xfr->task_probe->lookup_target->host) ? > xfr->task_probe->lookup_target->host : "null"), > (xfr->task_probe->lookup_aaaa?"AAAA":"A")); > } > } > - if(xfr->task_probe->lookup_target->list && > + if(xfr->task_probe->lookup_target && > + xfr->task_probe->lookup_target->list && > xfr->task_probe->lookup_target == > xfr_probe_current_master(xfr)) > xfr->task_probe->scan_addr = > xfr->task_probe->lookup_target->list; > > @@ -6966,8 +7253,8 @@ xfr_start_probe(struct auth_xfer* xfr, s > if(!have_probe_targets(xfr->task_probe->masters) && > xfr->task_probe->masters != NULL) > xfr->task_probe->only_lookup = 1; > - if(!(xfr->task_probe->only_lookup && > - xfr->task_probe->masters != NULL)) { > + if(!xfr->task_probe->only_lookup && > + !have_probe_targets(xfr->task_probe->masters)) { > /* useless to pick up task_probe, no masters to > * probe. Instead attempt to pick up task transfer > */ > if(xfr->task_transfer->worker == NULL) { > @@ -7170,6 +7457,8 @@ auth_xfer_new(struct auth_zone* z) > xfr->namelen = z->namelen; > xfr->namelabs = z->namelabs; > xfr->dclass = z->dclass; > + xfr->max_transfer_size = z->max_transfer_size; > + xfr->max_transfer_time = z->max_transfer_time; > > xfr->task_nextprobe = (struct auth_nextprobe*)calloc(1, > sizeof(struct auth_nextprobe)); > @@ -7379,35 +7668,48 @@ xfer_set_masters(struct auth_master** li > { > 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; > } > } > @@ -7932,7 +8234,8 @@ static int zonemd_dnssec_verify_rrset(st > "zonemd: verify %s RRset with DNSKEY", typestr); > } > sec = dnskeyset_verify_rrset(env, ve, &pk, dnskey, sigalg, > why_bogus, NULL, > - LDNS_SECTION_ANSWER, NULL, &verified, reasonbuf, > reasonlen); > + LDNS_SECTION_ANSWER, NULL, NULL, &verified, reasonbuf, > + reasonlen); > if(sec == sec_status_secure) { > return 1; > } > @@ -8281,8 +8584,8 @@ zonemd_get_dnskey_from_anchor(struct aut > auth_zone_log(z->name, VERB_QUERY, > "zonemd: verify DNSKEY RRset with trust anchor"); > sec = val_verify_DNSKEY_with_TA(env, ve, keystorage, > anchor->ds_rrset, > - anchor->dnskey_rrset, NULL, why_bogus, NULL, NULL, > reasonbuf, > - reasonlen); > + anchor->dnskey_rrset, NULL, why_bogus, NULL, NULL, NULL, > + reasonbuf, reasonlen); > regional_free_all(env->scratch); > if(sec == sec_status_secure) { > /* success */ > @@ -8342,7 +8645,7 @@ auth_zone_verify_zonemd_key_with_ds(stru > keystorage->rk.rrset_class = htons(z->dclass); > auth_zone_log(z->name, VERB_QUERY, "zonemd: verify zone DNSKEY > with DS"); > sec = val_verify_DNSKEY_with_DS(env, ve, keystorage, ds, sigalg, > - why_bogus, NULL, NULL, reasonbuf, reasonlen); > + why_bogus, NULL, NULL, NULL, reasonbuf, reasonlen); > regional_free_all(env->scratch); > if(sec == sec_status_secure) { > /* success */ > @@ -8371,9 +8674,13 @@ void auth_zonemd_dnskey_lookup_callback( > char reasonbuf[256]; > char* reason = NULL, *ds_bogus = NULL, *typestr="DNSKEY"; > struct ub_packed_rrset_key* dnskey = NULL, *ds = NULL; > - int is_insecure = 0, downprot; > + int is_insecure = 0, downprot, perform_write = 0; > struct ub_packed_rrset_key keystorage; > uint8_t sigalg[ALGO_NEEDS_MAX+1]; > + uint8_t bakname[LDNS_MAX_DOMAINLEN]; > + size_t baknamelen; > + uint16_t bakdclass; > + struct auth_chunk* chunk_list = NULL; > > lock_rw_wrlock(&z->lock); > env = z->zonemd_callback_env; > @@ -8496,7 +8803,37 @@ void auth_zonemd_dnskey_lookup_callback( > auth_zone_verify_zonemd_with_key(z, env, &env->mesh->mods, dnskey, > is_insecure, NULL, downprot?sigalg:NULL); > regional_free_all(env->scratch); > + > + if(z->zonemd_callback_perform_write) { > + if(!z->zone_expired) { > + /* Write to zonefile if the ZONEMD is okay. */ > + perform_write = 1; > + /* copy the key to lookup the z structure. > + * The new lookup is readonly so concurrent > + * queries can continue. */ > + if(z->namelen > sizeof(bakname)) { > + perform_write = 0; > + > auth_chunk_list_delete(z->perform_write_chunk_list); > + z->perform_write_chunk_list = NULL; > + } else { > + memcpy(bakname, z->name, z->namelen); > + baknamelen = z->namelen; > + bakdclass = z->dclass; > + chunk_list = z->perform_write_chunk_list; > + z->perform_write_chunk_list = NULL; > + } > + } else { > + > auth_chunk_list_delete(z->perform_write_chunk_list); > + z->perform_write_chunk_list = NULL; > + } > + z->zonemd_callback_perform_write = 0; > + } > lock_rw_unlock(&z->lock); > + > + if(perform_write) { > + zone_write_after_update_reacq(bakname, baknamelen, > bakdclass, > + env, chunk_list); > + } > } > > /** lookup DNSKEY for ZONEMD verification */ > @@ -8561,8 +8898,12 @@ zonemd_lookup_dnskey(struct auth_zone* z > /* the callback can be called straight away */ > lock_rw_unlock(&z->lock); > if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, > - &auth_zonemd_dnskey_lookup_callback, z, 0)) { > + &auth_zonemd_dnskey_lookup_callback, z, 0, > + &z->zonemd_callback_unique_info)) { > lock_rw_wrlock(&z->lock); > + /* no callback will run; do not leave the pending > + * marker set */ > + z->zonemd_callback_env = NULL; > log_err("out of memory lookup of %s for zonemd", > (fetch_ds?"DS":"DNSKEY")); > return 0; > Index: services/authzone.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/authzone.h,v > diff -u -p -r1.15 authzone.h > --- services/authzone.h 26 Sep 2025 07:32:37 -0000 1.15 > +++ services/authzone.h 20 Sep 2026 09:50:48 -0000 > @@ -144,6 +144,12 @@ struct auth_zone { > struct module_env* zonemd_callback_env; > /** for the zonemd callback, the type of data looked up */ > uint16_t zonemd_callback_qtype; > + /** for the zonemd callback, the unique info */ > + void* zonemd_callback_unique_info; > + /** if the zonemd callback should write to file */ > + int zonemd_callback_perform_write; > + /** chunklist to write for chunked transfer. */ > + struct auth_chunk* perform_write_chunk_list; > /** zone has been deleted */ > int zone_deleted; > /** deletelist pointer, unused normally except during delete */ > @@ -153,6 +159,10 @@ struct auth_zone { > struct auth_zone* rpz_az_next; > /** previous auth zone containing RPZ data, or NULL */ > struct auth_zone* rpz_az_prev; > + /** The maximum auth zone transfer size, in bytes. */ > + size_t max_transfer_size; > + /** The maximum auth zone transfer time taken, in msec. */ > + int max_transfer_time; > }; > > /** > @@ -283,6 +293,15 @@ struct auth_xfer { > * this is renewed every SOA probe and transfer. On zone load > * from zonefile it is also set (with probe set soon to check) */ > time_t lease_time; > + > + /** The maximum auth zone transfer size, in bytes. */ > + size_t max_transfer_size; > + /** The maximum auth zone transfer time taken, in msec. */ > + int max_transfer_time; > + /** the zone is an rpz zone */ > + int is_rpz; > + /** the number of IXFRs since the last full transfer. */ > + int num_ixfrs; > }; > > /** > @@ -331,6 +350,8 @@ struct auth_probe { > > /** for the hostname lookups, which master is current */ > struct auth_master* lookup_target; > + /** for the lookup, the callback unique info */ > + void* lookup_unique_info; > /** are we looking up A or AAAA, first A, then AAAA (if ip6 > enabled) */ > int lookup_aaaa; > /** we only want to do lookups for making config work (for notify), > @@ -379,12 +400,18 @@ struct auth_transfer { > struct auth_chunk* chunks_first; > /** last element in chunks list (to append new data at the end) */ > struct auth_chunk* chunks_last; > + /** running total of bytes held in chunks_first..chunks_last */ > + size_t chunks_total; > + /** start time of the transfer */ > + struct timeval start_time; > > /** list of upstream masters for this zone, from config */ > struct auth_master* masters; > > /** for the hostname lookups, which master is current */ > struct auth_master* lookup_target; > + /** for the lookup, the callback unique info */ > + void* lookup_unique_info; > /** are we looking up A or AAAA, first A, then AAAA (if ip6 > enabled) */ > int lookup_aaaa; > > @@ -827,5 +854,11 @@ void auth_xfer_delete(struct auth_xfer* > * @param worker: the worker for which to stop tasks. > */ > void xfr_disown_tasks(struct auth_xfer* xfr, struct worker* worker); > + > +/** count number of open and closed parenthesis in a chunkline */ > +int chunkline_count_parens(struct sldns_buffer* buf, size_t start); > + > +/** Clear data in auth zone */ > +void auth_zone_clear_data(struct auth_zone* z); > > #endif /* SERVICES_AUTHZONE_H */ > Index: services/listen_dnsport.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/listen_dnsport.c,v > diff -u -p -r1.42 listen_dnsport.c > --- services/listen_dnsport.c 27 Jul 2026 14:14:39 -0000 1.42 > +++ services/listen_dnsport.c 20 Sep 2026 09:50:48 -0000 > @@ -1125,7 +1125,7 @@ make_sock_port(int stype, const char* if > int use_systemd, int dscp, struct unbound_socket* ub_sock, > const char* additional) > { > - char* s = strchr(ifname, '@'); > + const char* s = strchr(ifname, '@'); > if(s) { > /* override port with ifspec@port */ > int port; > @@ -2133,7 +2133,7 @@ void listen_start_accept(struct listen_d > } > > struct tcp_req_info* > -tcp_req_info_create(struct sldns_buffer* spoolbuf) > +tcp_req_info_create(struct comm_base* base, struct sldns_buffer* spoolbuf) > { > struct tcp_req_info* req = (struct > tcp_req_info*)malloc(sizeof(*req)); > if(!req) { > @@ -2141,6 +2141,12 @@ tcp_req_info_create(struct sldns_buffer* > return NULL; > } > memset(req, 0, sizeof(*req)); > + req->read_again_timer = comm_timer_create(base, tcp_read_again_cb, > req); > + if(!req->read_again_timer) { > + log_err("malloc failure"); > + free(req); > + return NULL; > + } > req->spool_buffer = spoolbuf; > return req; > } > @@ -2150,6 +2156,7 @@ tcp_req_info_delete(struct tcp_req_info* > { > if(!req) return; > tcp_req_info_clear(req); > + comm_timer_delete(req->read_again_timer); > /* cp is pointer back to commpoint that owns this struct and > * called delete on us */ > /* spool_buffer is shared udp buffer, not deleted here */ > @@ -2167,7 +2174,7 @@ void tcp_req_info_clear(struct tcp_req_i > while(open) { > nopen = open->next; > mesh_state_remove_reply(open->mesh, open->mesh_state, > req->cp, > - NULL); > + NULL, NULL); > free(open); > open = nopen; > } > @@ -2189,6 +2196,9 @@ void tcp_req_info_clear(struct tcp_req_i > req->done_req_list = NULL; > req->num_done_req = 0; > req->read_is_closed = 0; > + > + if(comm_timer_is_set(req->read_again_timer)) > + comm_timer_disable(req->read_again_timer); > } > > void > @@ -3617,7 +3627,7 @@ stream_tree_del(rbnode_type* node, void* > stream = (struct doq_stream*)node; > if(stream->mesh_state) { > mesh_state_remove_reply(stream->mesh, stream->mesh_state, > - args->conn->doq_socket->cp, stream); > + args->conn->doq_socket->cp, NULL, stream); > stream->mesh_state = NULL; > } > if(stream->in) > @@ -3639,7 +3649,8 @@ doq_conn_delete(struct doq_conn* conn, s > lock_rw_unlock(&conn->table->conid_lock); > /* Remove the app data from ngtcp2 before SSL_free of conn->ssl, > * because the ngtcp2 conn is deleted. */ > - SSL_set_app_data(conn->ssl, NULL); > + if(conn->ssl) > + SSL_set_app_data(conn->ssl, NULL); > if(conn->stream_tree.count != 0) { > struct doq_stream_tree_del_args args; > memset(&args, 0, sizeof(args)); > @@ -3956,7 +3967,7 @@ doq_stream_close(struct doq_conn* conn, > stream->is_closed = 1; > if(stream->mesh_state) { > mesh_state_remove_reply(stream->mesh, stream->mesh_state, > - conn->doq_socket->cp, stream); > + conn->doq_socket->cp, NULL, stream); > stream->mesh_state = NULL; > } > doq_stream_off_write_list(conn, stream); > @@ -4503,7 +4514,7 @@ doq_stream_reset_cb(ngtcp2_conn* ATTR_UN > "unknown stream %d", (int)stream_id); > return 0; > } > - if(!doq_stream_close(doq_conn, stream, 0)) > + if(!doq_stream_close(doq_conn, stream, 1)) > return NGTCP2_ERR_CALLBACK_FAILURE; > return 0; > } > @@ -4851,7 +4862,7 @@ doq_ssl_server_setup(SSL_CTX* ctx, struc > SSL_set_app_data(ssl, conn); > #endif > SSL_set_accept_state(ssl); > -#ifdef USE_NGTCP2_CRYPTO_OSSL > +#ifdef HAVE_SSL_SET_QUIC_TLS_EARLY_DATA_ENABLED > SSL_set_quic_tls_early_data_enabled(ssl, 1); > #else > SSL_set_quic_early_data_enabled(ssl, 1); > @@ -4960,6 +4971,7 @@ doq_conn_setup(struct doq_conn* conn, ui > rv = ngtcp2_conn_server_new(&conn->conn, &scid_cid, &sv_scid, > &path, > conn->version, &callbacks, &settings, ¶ms, NULL, conn); > if(rv != 0) { > + conn->conn = NULL; > lock_rw_unlock(&conn->table->conid_lock); > log_err("ngtcp2_conn_server_new failed: %s", > ngtcp2_strerror(rv)); > Index: services/listen_dnsport.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/listen_dnsport.h,v > diff -u -p -r1.24 listen_dnsport.h > --- services/listen_dnsport.h 27 Jul 2026 14:14:39 -0000 1.24 > +++ services/listen_dnsport.h 20 Sep 2026 09:50:48 -0000 > @@ -347,6 +347,10 @@ struct tcp_req_info { > int num_done_req; > /** list of pending writable result packets, malloced one at a > time */ > struct tcp_req_done_item* done_req_list; > + /** the read again timer, when the number of pipelined TCP queries > + * is large, it waits, zero time, for a new event loop to service > + * the remainder of the TCP traffic on the fd. */ > + struct comm_timer* read_again_timer; > }; > > /** > @@ -377,10 +381,12 @@ struct tcp_req_done_item { > * Create tcp request info structure that keeps track of open > * requests on the TCP channel that are resolved at the same time, > * and the pending results that have to get written back to that client. > + * @param base: comm base for read again timer. > * @param spoolbuf: shared buffer > * @return new structure or NULL on alloc failure. > */ > -struct tcp_req_info* tcp_req_info_create(struct sldns_buffer* spoolbuf); > +struct tcp_req_info* tcp_req_info_create(struct comm_base* base, > + struct sldns_buffer* spoolbuf); > > /** > * Delete tcp request structure. Called by owning commpoint. > Index: services/localzone.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/localzone.c,v > diff -u -p -r1.25 localzone.c > --- services/localzone.c 26 May 2026 11:14:11 -0000 1.25 > +++ services/localzone.c 20 Sep 2026 09:50:48 -0000 > @@ -386,8 +386,6 @@ new_local_rrset(struct regional* region, > log_err("out of memory"); > return NULL; > } > - rrset->next = node->rrsets; > - node->rrsets = rrset; > rrset->rrset = (struct ub_packed_rrset_key*) > regional_alloc_zero(region, sizeof(*rrset->rrset)); > if(!rrset->rrset) { > @@ -408,6 +406,8 @@ new_local_rrset(struct regional* region, > rrset->rrset->rk.dname_len = node->namelen; > rrset->rrset->rk.type = htons(rrtype); > rrset->rrset->rk.rrset_class = htons(rrclass); > + rrset->next = node->rrsets; > + node->rrsets = rrset; > return rrset; > } > > @@ -431,6 +431,10 @@ rrset_insert_rr(struct regional* region, > pd->rr_ttl = regional_alloc(region, sizeof(*pd->rr_ttl)*pd->count); > pd->rr_data = regional_alloc(region, > sizeof(*pd->rr_data)*pd->count); > if(!pd->rr_len || !pd->rr_ttl || !pd->rr_data) { > + pd->count--; > + pd->rr_len = oldlen; > + pd->rr_ttl = oldttl; > + pd->rr_data = olddata; > log_err("out of memory"); > return 0; > } > @@ -446,6 +450,10 @@ rrset_insert_rr(struct regional* region, > pd->rr_ttl[0] = ttl; > pd->rr_data[0] = regional_alloc_init(region, rdata, rdata_len); > if(!pd->rr_data[0]) { > + pd->count--; > + pd->rr_len = oldlen; > + pd->rr_ttl = oldttl; > + pd->rr_data = olddata; > log_err("out of memory"); > return 0; > } > @@ -671,7 +679,9 @@ lz_enter_rr_str(struct local_zones* zone > z = local_zones_lookup(zones, rr_name, len, labs, rr_class, > rr_type, 1); > if(!z) { > lock_rw_unlock(&zones->lock); > - fatal_exit("internal error: no zone for rr %s", rr); > + log_err("internal error: no zone for rr %s", rr); > + free(rr_name); > + return 0; > } > lock_rw_wrlock(&z->lock); > lock_rw_unlock(&zones->lock); > @@ -1006,23 +1016,23 @@ static struct local_zone* find_closest_p > struct local_zone* prev) > { > struct local_zone* p; > - int m; > + int m; > if(!prev || prev->dclass != curr->dclass) return NULL; > (void)dname_lab_cmp(prev->name, prev->namelabs, curr->name, > curr->namelabs, &m); /* we know prev is smaller */ > - /* sort order like: . com. bla.com. zwb.com. net. */ > - /* find the previous, or parent-parent-parent */ > + /* sort order like: . com. bla.com. zwb.com. net. */ > + /* find the previous, or parent-parent-parent */ > for(p = prev; p; p = p->parent) { > - /* looking for name with few labels, a parent */ > - if(p->namelabs <= m) { > - /* ==: since prev matched m, this is > closest*/ > - /* <: prev matches more, but is not a > parent, > - * this one is a (grand)parent */ > + /* looking for name with few labels, a parent */ > + if(p->namelabs <= m) { > + /* ==: since prev matched m, this is closest*/ > + /* <: prev matches more, but is not a parent, > + * this one is a (grand)parent */ > return p; > } > } > return NULL; > - } > +} > > /** setup parent pointers, so that a lookup can be done for closest match > */ > void > @@ -1037,7 +1047,7 @@ lz_init_parents(struct local_zones* zone > if(node->override_tree) > addr_tree_init_parents(node->override_tree); > lock_rw_unlock(&node->lock); > - } > + } > lock_rw_unlock(&zones->lock); > } > > @@ -1500,8 +1510,10 @@ find_tag_datas(struct query_info* qinfo, > return 0; /* out of memory */ > qinfo->local_alias->rrset = > regional_alloc_init(temp, r, sizeof(*r)); > - if(!qinfo->local_alias->rrset) > + if(!qinfo->local_alias->rrset) { > + qinfo->local_alias = NULL; > return 0; /* out of memory */ > + } > } > return result; > } > @@ -1567,13 +1579,17 @@ local_data_answer(struct local_zone* z, > return 0; /* out of memory */ > qinfo->local_alias->rrset = regional_alloc_init( > temp, lr->rrset, sizeof(*lr->rrset)); > - if(!qinfo->local_alias->rrset) > + if(!qinfo->local_alias->rrset) { > + qinfo->local_alias = NULL; > return 0; /* out of memory */ > + } > qinfo->local_alias->rrset->rk.dname = qinfo->qname; > qinfo->local_alias->rrset->rk.dname_len = qinfo->qname_len; > get_cname_target(lr->rrset, &ctarget, &ctargetlen); > - if(!ctargetlen) > + if(!ctargetlen) { > + qinfo->local_alias = NULL; > return 0; /* invalid cname */ > + } > if(dname_is_wild(ctarget)) { > /* synthesize cname target */ > struct packed_rrset_data* d, *lr_d; > @@ -1602,8 +1618,10 @@ local_data_answer(struct local_zone* z, > sizeof(struct packed_rrset_data) + > sizeof(size_t) + > sizeof(uint8_t*) + sizeof(time_t) + > sizeof(uint16_t) > + newtargetlen); > - if(!d) > + if(!d) { > + qinfo->local_alias = NULL; > return 0; /* out of memory */ > + } > lr_d = (struct > packed_rrset_data*)lr->rrset->entry.data; > qinfo->local_alias->rrset->entry.data = d; > d->ttl = lr_d->rr_ttl[0]; /* RFC6672-like behavior: > @@ -1650,7 +1668,7 @@ local_zone_does_not_cover(struct local_z > 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 > @@ -1661,7 +1679,9 @@ local_zone_does_not_cover(struct local_z > 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); > @@ -1727,7 +1747,8 @@ local_zones_zone_answer(struct local_zon > || 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, > @@ -1737,6 +1758,17 @@ local_zones_zone_answer(struct local_zon > } > > return 0; > + } 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, > + 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, > * used for blocklists. */ > @@ -1904,7 +1936,10 @@ local_zones_answer(struct local_zones* z > 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 || > + 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; > @@ -1953,6 +1988,7 @@ local_zones_answer(struct local_zones* z > 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 > @@ -1984,6 +2020,9 @@ const char* local_zone_type2str(enum loc > 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_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"; > @@ -2020,6 +2059,12 @@ int local_zone_str2type(const char* type > *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, "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) > Index: services/localzone.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/localzone.h,v > diff -u -p -r1.16 localzone.h > --- services/localzone.h 27 Jul 2026 14:14:39 -0000 1.16 > +++ services/localzone.h 20 Sep 2026 09:50:48 -0000 > @@ -93,6 +93,12 @@ 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, > + /** 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 */ > Index: services/mesh.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/mesh.c,v > diff -u -p -r1.35 mesh.c > --- services/mesh.c 27 Jul 2026 14:14:39 -0000 1.35 > +++ services/mesh.c 20 Sep 2026 09:50:48 -0000 > @@ -373,7 +373,7 @@ mesh_serve_expired_lookup(struct module_ > "validation"); > goto bail_out; /* need to validate cache entry first */ > } else if(msg->rep->security == sec_status_secure && > - !reply_all_rrsets_secure(msg->rep) && must_validate) { > + !reply_an_ns_rrsets_secure(msg->rep) && must_validate) { > verbose(VERB_ALGO, "Serve expired: secure entry" > " changed status"); > goto bail_out; /* rrset changed, re-verify */ > @@ -424,6 +424,44 @@ mesh_serve_expired_init(struct mesh_stat > return 1; > } > > +/** remove a reply without accounting, rollback the add reply. */ > +static void > +mesh_remove_reply_without_accounting(struct mesh_state* s, > + struct mesh_reply* todel) > +{ > + struct mesh_reply* r, *prev = NULL; > + for(r = s->reply_list; r; r = r->next) { > + if(r == todel) { > + if(prev) > + prev->next = r->next; > + else s->reply_list = r->next; > + r->next = NULL; > + /* todel is allocated in region */ > + return; > + } > + prev = r; > + } > +} > + > +/** remove a callback without accounting, rollback the add reply. */ > +static void > +mesh_remove_callback_without_accounting(struct mesh_state* s, > + struct mesh_cb* todel) > +{ > + struct mesh_cb* r, *prev = NULL; > + for(r = s->cb_list; r; r = r->next) { > + if(r == todel) { > + if(prev) > + prev->next = r->next; > + else s->cb_list = r->next; > + r->next = NULL; > + /* todel is allocated in region */ > + return; > + } > + prev = r; > + } > +} > + > void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, > struct respip_client_info* cinfo, uint16_t qflags, > struct edns_data* edns, struct comm_reply* rep, uint16_t qid, > @@ -433,7 +471,8 @@ void mesh_new_client(struct mesh_area* m > int unique = unique_mesh_state(edns->opt_list_in, mesh->env); > int was_detached = 0; > int was_noreply = 0; > - int added = 0; > + int added = 0, added_reply_without_accounting = 0, added_tcp = 0; > + struct mesh_reply* repadded = NULL; > int timeout = mesh->env->cfg->serve_expired? > mesh->env->cfg->serve_expired_client_timeout:0; > struct sldns_buffer* r_buffer = rep->c->buffer; > @@ -544,16 +583,18 @@ void mesh_new_client(struct mesh_area* m > } > } > /* add reply to s */ > - if(!mesh_state_add_reply(s, edns, rep, qid, qflags, qinfo)) { > + if(!mesh_state_add_reply(s, edns, rep, qid, qflags, qinfo, > &repadded)) { > log_err("mesh_new_client: out of memory; SERVFAIL"); > goto servfail_mem; > } > + added_reply_without_accounting = 1; > if(rep->c->tcp_req_info) { > if(!tcp_req_info_add_meshstate(rep->c->tcp_req_info, mesh, > s)) { > log_err("mesh_new_client: out of memory add > tcpreqinfo"); > goto servfail_mem; > } > } > + added_tcp = 1; > 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) { > @@ -575,6 +616,8 @@ void mesh_new_client(struct mesh_area* m > } > } > #endif > + /* Since the acccounting now happens, > + * added_reply_without_accounting = 0; but that is not used. */ > infra_wait_limit_inc(mesh->env->infra_cache, rep, *mesh->env->now, > mesh->env->cfg); > /* update statistics */ > @@ -614,6 +657,11 @@ servfail_mem: > 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_reply_without_accounting) { > + mesh_remove_reply_without_accounting(s, repadded); > + if(added_tcp && rep->c->tcp_req_info) > + > tcp_req_info_remove_mesh_state(rep->c->tcp_req_info, s); > + } > if(added) > mesh_state_delete(&s->s); > return; > @@ -622,7 +670,8 @@ servfail_mem: > int > mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, > uint16_t qflags, struct edns_data* edns, sldns_buffer* buf, > - uint16_t qid, mesh_cb_func_type cb, void* cb_arg, int rpz_passthru) > + uint16_t qid, mesh_cb_func_type cb, void* cb_arg, int rpz_passthru, > + void** unique_info) > { > struct mesh_state* s = NULL; > int unique = unique_mesh_state(edns->opt_list_in, mesh->env); > @@ -631,6 +680,7 @@ mesh_new_callback(struct mesh_area* mesh > int was_detached = 0; > int was_noreply = 0; > int added = 0; > + struct mesh_cb* add_cb = NULL; > uint16_t mesh_flags = qflags&(BIT_RD|BIT_CD); > if(!unique) > s = mesh_area_find(mesh, NULL, qinfo, mesh_flags, 0, 0); > @@ -676,13 +726,14 @@ mesh_new_callback(struct mesh_area* mesh > } > } > /* add reply to s */ > - if(!mesh_state_add_cb(s, edns, buf, cb, cb_arg, qid, qflags)) { > + if(!mesh_state_add_cb(s, edns, buf, cb, cb_arg, qid, qflags, > &add_cb)) { > if(added) > mesh_state_delete(&s->s); > return 0; > } > /* add serve expired timer if not already there */ > if(timeout && !mesh_serve_expired_init(s, timeout)) { > + mesh_remove_callback_without_accounting(s, add_cb); > if(added) > mesh_state_delete(&s->s); > return 0; > @@ -693,6 +744,7 @@ mesh_new_callback(struct mesh_area* mesh > (mesh->env->cachedb_enabled && > mesh->env->cfg->cachedb_check_when_serve_expired)) { > if(!mesh_serve_expired_init(s, -1)) { > + mesh_remove_callback_without_accounting(s, add_cb); > if(added) > mesh_state_delete(&s->s); > return 0; > @@ -708,6 +760,8 @@ mesh_new_callback(struct mesh_area* mesh > mesh->num_reply_states ++; > } > mesh->num_reply_addrs++; > + if(unique_info) > + *unique_info = s->unique; > if(added) > mesh_run(mesh, s, module_event_new, NULL); > return 1; > @@ -911,32 +965,9 @@ void mesh_report_reply(struct mesh_area* > 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) > @@ -955,20 +986,13 @@ mesh_copy_client_info(struct regional* r > 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 > @@ -1073,6 +1097,18 @@ mesh_state_make_unique(struct mesh_state > mstate->unique = mstate; > } > > +/** pop a reply from the reply list, if there are any. */ > +static struct mesh_reply* > +mesh_reply_list_pop_first(struct mesh_state* mstate) > +{ > + if(mstate->reply_list) { > + struct mesh_reply* r = mstate->reply_list; > + mstate->reply_list = r->next; > + return r; > + } > + return NULL; > +} > + > void > mesh_state_cleanup(struct mesh_state* mstate) > { > @@ -1088,23 +1124,30 @@ mesh_state_cleanup(struct mesh_state* ms > } > /* drop unsent replies */ > if(!mstate->replies_sent) { > - struct mesh_reply* rep = mstate->reply_list; > + struct mesh_reply* rep; > struct mesh_cb* cb; > - /* One http2 stream could bring down its comm_point along > with > - * the other streams which could share the same query. Do > all > - * the http2 stream bookkeeping upfront. */ > - for(; rep; rep=rep->next) { > - if(rep->query_reply.c->use_h2) > - > http2_stream_remove_mesh_state(rep->h2_stream); > - } > - rep = mstate->reply_list; > - /* in tcp_req_info, the mstates linked are removed, but > - * the reply_list is now NULL, so the > remove-from-empty-list > - * takes no time and also it does not do the mesh > accounting */ > - mstate->reply_list = NULL; > - for(; rep; rep=rep->next) { > + /* Pop items from the list, that means there is no > iterator. > + * And then items can be removed from the reply list, from > + * like comm_point_drop_reply and comm_point_close calls. > + * As the tcp_req_info and http2 code drops the entire > + * connection. That could delete mesh_reply items previous > and > + * after the current state. The previous items are already > + * popped. And the next items can be altered, like to when > a > + * connection has more replies on the reply list. > + * The current item is also popped so the code needs to > + * remove its references. */ > + while((rep = mesh_reply_list_pop_first(mstate)) != NULL) { > infra_wait_limit_dec(mesh->env->infra_cache, > &rep->query_reply, mesh->env->cfg); > + if(rep->query_reply.c->tcp_req_info) > + tcp_req_info_remove_mesh_state( > + rep->query_reply.c->tcp_req_info, > + mstate); > + else if(rep->query_reply.c->use_h2) > + > http2_stream_remove_mesh_state(rep->h2_stream); > + else if(rep->query_reply.doq_stream) > + doq_stream_remove_mesh_state( > + rep->query_reply.doq_stream); > comm_point_drop_reply(&rep->query_reply); > log_assert(mesh->num_reply_addrs > 0); > mesh->num_reply_addrs--; > @@ -1241,6 +1284,9 @@ int mesh_add_sub(struct module_qstate* q > log_err("mesh_attach_sub: out of memory"); > return 0; > } > + /* inherit RPZ passthru from the parent so respip on the > sub > + * sees the same client-IP/qname PASSTHRU decision */ > + (*sub)->s.rpz_passthru = qstate->rpz_passthru; > #ifdef UNBOUND_DEBUG > n = > #else > @@ -1465,12 +1511,6 @@ mesh_send_reply(struct mesh_state* m, in > struct timeval end_time; > struct timeval duration; > int secure; > - /* briefly set the replylist to null in case the > - * meshsendreply calls tcpreqinfo sendreply that > - * comm_point_drops because of size, and then the > - * null stops the mesh state remove and thus > - * reply_list modification and accounting */ > - struct mesh_reply* rlist = m->reply_list; > > /* rpz: apply actions */ > rcode = mesh_is_udp(r) && mesh_is_rpz_respip_tcponly_action(m) > @@ -1527,9 +1567,7 @@ mesh_send_reply(struct mesh_state* m, in > sldns_buffer_write_at(r_buffer, 0, &r->qid, > sizeof(uint16_t)); > sldns_buffer_write_at(r_buffer, 12, r->qname, > m->s.qinfo.qname_len); > - m->reply_list = NULL; > comm_point_send_reply(&r->query_reply); > - m->reply_list = rlist; > } else if(rcode) { > m->s.qinfo.qname = r->qname; > m->s.qinfo.local_alias = r->local_alias; > @@ -1551,9 +1589,7 @@ mesh_send_reply(struct mesh_state* m, in > } > error_encode(r_buffer, rcode, &m->s.qinfo, r->qid, > r->qflags, &r->edns); > - m->reply_list = NULL; > comm_point_send_reply(&r->query_reply); > - m->reply_list = rlist; > } else { > size_t udp_size = r->edns.udp_size; > r->edns.edns_version = EDNS_ADVERTISED_VERSION; > @@ -1589,9 +1625,7 @@ mesh_send_reply(struct mesh_state* m, in > error_encode(r_buffer, LDNS_RCODE_SERVFAIL, > &m->s.qinfo, r->qid, r->qflags, &r->edns); > } > - m->reply_list = NULL; > comm_point_send_reply(&r->query_reply); > - m->reply_list = rlist; > } > infra_wait_limit_dec(m->s.env->infra_cache, &r->query_reply, > m->s.env->cfg); > @@ -1743,6 +1777,7 @@ void mesh_query_done(struct mesh_state* > struct reply_info* rep = (mstate->s.return_msg? > mstate->s.return_msg->rep:NULL); > struct timeval tv = {0, 0}; > + struct mesh_area* mesh = mstate->s.env->mesh; > int i = 0; > /* No need for the serve expired timer anymore; we are going to > reply. */ > if(mstate->s.serve_expired_data) { > @@ -1763,10 +1798,22 @@ void mesh_query_done(struct mesh_state* > } > } > > - if(mstate->reply_list && mstate->s.env->cfg->dns_error_reporting) > + if(mstate->reply_list && mstate->s.env->cfg->dns_error_reporting > + && (!rep || rep->security != sec_status_secure)) > dns_error_reporting(&mstate->s, rep); > > - for(r = mstate->reply_list; r; r = r->next) { > + while((r = mesh_reply_list_pop_first(mstate)) != NULL) { > + > + /* it was not detached (because it had a reply list), > could be now */ > + if(!mstate->reply_list && !mstate->cb_list > + && mstate->super_set.count == 0) { > + mesh->num_detached_states++; > + } > + /* if not replies any more in mstate, it is no longer a > reply_state */ > + if(!mstate->reply_list && !mstate->cb_list) { > + log_assert(mesh->num_reply_states > 0); > + mesh->num_reply_states--; > + } > if(mesh_is_udp(r)) { > /* For UDP queries, the old replies are discarded. > * This stops a large volume of old replies from > @@ -1781,22 +1828,18 @@ void mesh_query_done(struct mesh_state* > > ((int)old.tv_sec)*1000+((int)old.tv_usec)/1000 > > mstate->s.env->cfg->discard_timeout) { > /* Drop the reply, it is too old */ > - /* briefly set the reply_list to NULL, so > that the > - * tcp req info cleanup routine that calls > the mesh > - * to deregister the meshstate for it is > not done > - * because the list is NULL and also > accounting is not > - * done there, but instead we do that > here. */ > - struct mesh_reply* reply_list = > mstate->reply_list; > verbose(VERB_ALGO, "drop reply, it is > older than discard-timeout"); > > infra_wait_limit_dec(mstate->s.env->infra_cache, > &r->query_reply, > mstate->s.env->cfg); > - mstate->reply_list = NULL; > - if(r->query_reply.c->use_h2) > + if(r->query_reply.c->tcp_req_info) > + tcp_req_info_remove_mesh_state( > + > r->query_reply.c->tcp_req_info, > + mstate); > + else if(r->query_reply.c->use_h2) > > http2_stream_remove_mesh_state(r->h2_stream); > 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); > mstate->s.env->mesh->num_reply_addrs--; > > mstate->s.env->mesh->num_queries_discard_timeout++; > @@ -1821,22 +1864,17 @@ void mesh_query_done(struct mesh_state* > /* if this query is determined to be dropped during the > * mesh processing, this is the point to take that action. > */ > if(mstate->s.is_drop) { > - /* briefly set the reply_list to NULL, so that the > - * tcp req info cleanup routine that calls the mesh > - * to deregister the meshstate for it is not done > - * because the list is NULL and also accounting is > not > - * done there, but instead we do that here. */ > - struct mesh_reply* reply_list = mstate->reply_list; > infra_wait_limit_dec(mstate->s.env->infra_cache, > &r->query_reply, mstate->s.env->cfg); > - mstate->reply_list = NULL; > - if(r->query_reply.c->use_h2) { > + if(r->query_reply.c->tcp_req_info) { > + tcp_req_info_remove_mesh_state( > + r->query_reply.c->tcp_req_info, > mstate); > + } else if(r->query_reply.c->use_h2) { > > http2_stream_remove_mesh_state(r->h2_stream); > } 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); > mstate->s.env->mesh->num_reply_addrs--; > } else { > @@ -1877,18 +1915,6 @@ void mesh_query_done(struct mesh_state* > } > } > > - /* Mesh area accounting */ > - if(mstate->reply_list) { > - mstate->reply_list = NULL; > - if(!mstate->reply_list && !mstate->cb_list) { > - /* was a reply state, not anymore */ > - log_assert(mstate->s.env->mesh->num_reply_states > > 0); > - mstate->s.env->mesh->num_reply_states--; > - } > - if(!mstate->reply_list && !mstate->cb_list && > - mstate->super_set.count == 0) > - mstate->s.env->mesh->num_detached_states++; > - } > mstate->replies_sent = 1; > > while((c = mstate->cb_list) != NULL) { > @@ -1946,6 +1972,25 @@ struct mesh_state* mesh_area_find(struct > return result; > } > > +struct mesh_state* mesh_area_find_unique(struct mesh_area* mesh, > + struct respip_client_info* cinfo, struct query_info* qinfo, > + uint16_t qflags, int prime, int valrec, void* unique_info) > +{ > + struct mesh_state key; > + struct mesh_state* result; > + > + key.node.key = &key; > + key.s.is_priming = prime; > + key.s.is_valrec = valrec; > + key.s.qinfo = *qinfo; > + key.s.query_flags = qflags; > + key.unique = (struct mesh_state*)unique_info; > + key.s.client_info = cinfo; > + > + result = (struct mesh_state*)rbtree_search(&mesh->all, &key); > + return result; > +} > + > /** remove mesh state callback */ > int mesh_state_del_cb(struct mesh_state* s, mesh_cb_func_type cb, void* > cb_arg) > { > @@ -1967,7 +2012,7 @@ int mesh_state_del_cb(struct mesh_state* > > int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns, > sldns_buffer* buf, mesh_cb_func_type cb, void* cb_arg, > - uint16_t qid, uint16_t qflags) > + uint16_t qid, uint16_t qflags, struct mesh_cb** result) > { > struct mesh_cb* r = regional_alloc(s->s.region, > sizeof(struct mesh_cb)); > @@ -1991,13 +2036,14 @@ int mesh_state_add_cb(struct mesh_state* > r->qflags = qflags; > r->next = s->cb_list; > s->cb_list = r; > + *result = r; > return 1; > > } > > int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, > struct comm_reply* rep, uint16_t qid, uint16_t qflags, > - const struct query_info* qinfo) > + const struct query_info* qinfo, struct mesh_reply** result) > { > struct mesh_reply* r = regional_alloc(s->s.region, > sizeof(struct mesh_reply)); > @@ -2078,6 +2124,7 @@ int mesh_state_add_reply(struct mesh_sta > r->local_alias = NULL; > > s->reply_list = r; > + *result = r; > return 1; > } > > @@ -2235,8 +2282,29 @@ void mesh_run(struct mesh_area* mesh, st > 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)); > @@ -2388,7 +2456,8 @@ void mesh_list_remove(struct mesh_state* > } > > void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, > - struct comm_point* cp, struct doq_stream* doq_stream) > + struct comm_point* cp, struct http2_stream* h2_stream, > + struct doq_stream* doq_stream) > { > struct mesh_reply* n, *prev = NULL; > n = m->reply_list; > @@ -2397,6 +2466,7 @@ void mesh_state_remove_reply(struct mesh > if(!n) return; /* nothing to remove, also no accounting needed */ > while(n) { > if(n->query_reply.c == cp > + && (!h2_stream || n->h2_stream == h2_stream) > && (!doq_stream || n->query_reply.doq_stream == > doq_stream)) { > /* unlink it */ > if(prev) prev->next = n->next; > @@ -2434,7 +2504,6 @@ void mesh_state_remove_reply(struct mesh > } > } > > - > static int > apply_respip_action(struct module_qstate* qstate, > const struct query_info* qinfo, struct respip_client_info* cinfo, > @@ -2567,7 +2636,18 @@ mesh_serve_expired_callback(void* arg) > if(verbosity >= VERB_ALGO) > log_dns_msg("Serve expired lookup", &qstate->qinfo, > msg->rep); > > - for(r = mstate->reply_list; r; r = r->next) { > + while((r = mesh_reply_list_pop_first(mstate)) != NULL) { > + > + /* it was not detached (because it had a reply list), > could be now */ > + if(!mstate->reply_list && !mstate->cb_list > + && mstate->super_set.count == 0) { > + mesh->num_detached_states++; > + } > + /* if not replies any more in mstate, it is no longer a > reply_state */ > + if(!mstate->reply_list && !mstate->cb_list) { > + log_assert(mesh->num_reply_states > 0); > + mesh->num_reply_states--; > + } > if(mesh_is_udp(r)) { > struct timeval old; > timeval_subtract(&old, mstate->s.env->now_tv, > &r->start_time); > @@ -2575,22 +2655,17 @@ mesh_serve_expired_callback(void* arg) > ((int)old.tv_sec)*1000+((int)old.tv_usec)/1000 > > mstate->s.env->cfg->discard_timeout) { > /* Drop the reply, it is too old */ > - /* briefly set the reply_list to NULL, so that the > - * tcp req info cleanup routine that calls the mesh > - * to deregister the meshstate for it is not done > - * because the list is NULL and also accounting is > not > - * done there, but instead we do that here. */ > - struct mesh_reply* reply_list = mstate->reply_list; > verbose(VERB_ALGO, "drop reply, it is older than > discard-timeout"); > infra_wait_limit_dec(mstate->s.env->infra_cache, > &r->query_reply, mstate->s.env->cfg); > - mstate->reply_list = NULL; > - if(r->query_reply.c->use_h2) > + if(r->query_reply.c->tcp_req_info) > + tcp_req_info_remove_mesh_state( > + r->query_reply.c->tcp_req_info, > mstate); > + else if(r->query_reply.c->use_h2) > > http2_stream_remove_mesh_state(r->h2_stream); > 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); > mstate->s.env->mesh->num_reply_addrs--; > mstate->s.env->mesh->num_queries_discard_timeout++; > @@ -2628,8 +2703,7 @@ mesh_serve_expired_callback(void* arg) > if(r->query_reply.c->tcp_req_info) > > tcp_req_info_remove_mesh_state(r->query_reply.c->tcp_req_info, mstate); > /* mesh_send_reply removed mesh state from http2_stream. */ > - infra_wait_limit_dec(mstate->s.env->infra_cache, > - &r->query_reply, mstate->s.env->cfg); > + /* mesh_send_reply decremented wait_limit. */ > prev = r; > prev_buffer = r_buffer; > } > @@ -2648,18 +2722,6 @@ mesh_serve_expired_callback(void* arg) > } > } > > - /* Mesh area accounting */ > - if(mstate->reply_list) { > - mstate->reply_list = NULL; > - if(!mstate->reply_list && !mstate->cb_list) { > - log_assert(mesh->num_reply_states > 0); > - mesh->num_reply_states--; > - if(mstate->super_set.count == 0) { > - mesh->num_detached_states++; > - } > - } > - } > - > while((c = mstate->cb_list) != NULL) { > /* take this cb off the list; so that the list can be > * changed, eg. by adds from the callback routine */ > @@ -2692,13 +2754,30 @@ int mesh_jostle_exceeded(struct mesh_are > } > > void mesh_remove_callback(struct mesh_area* mesh, struct query_info* > qinfo, > - uint16_t qflags, mesh_cb_func_type cb, void* cb_arg) > + uint16_t qflags, mesh_cb_func_type cb, void* cb_arg, void* > unique_info) > { > struct mesh_state* s = NULL; > s = mesh_area_find(mesh, NULL, qinfo, qflags&(BIT_RD|BIT_CD), 0, > 0); > - if(!s) return; > - if(!mesh_state_del_cb(s, cb, cb_arg)) return; > + if(s && mesh_state_del_cb(s, cb, cb_arg)) > + goto removed; > + if(unique_info) { > + s = mesh_area_find_unique(mesh, NULL, qinfo, > + qflags&(BIT_RD|BIT_CD), 0, 0, unique_info); > + if(s && mesh_state_del_cb(s, cb, cb_arg)) > + goto removed; > + } > + /* mesh_area_find builds key.unique=NULL and cannot match a state > + * created with mesh_state_make_unique (e.g. subnetcache sets > + * env->unique_mesh). Fall back to a linear scan; cb+cb_arg is an > + * exact key (mesh_state_del_cb compares both). > + * This works for both lookups for zonemd and for hostname > authzone. */ > + RBTREE_FOR(s, struct mesh_state*, &mesh->all) { > + if(s->cb_list && mesh_state_del_cb(s, cb, cb_arg)) > + goto removed; > + } > + return; > > +removed: > /* It was in the list and removed. */ > log_assert(mesh->num_reply_addrs > 0); > mesh->num_reply_addrs--; > Index: services/mesh.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/mesh.h,v > diff -u -p -r1.17 mesh.h > --- services/mesh.h 27 Jul 2026 14:14:39 -0000 1.17 > +++ services/mesh.h 20 Sep 2026 09:50:48 -0000 > @@ -70,6 +70,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 > * dependency is failed (so that no loops can be created). > @@ -342,11 +349,14 @@ void mesh_new_client(struct mesh_area* m > * @param cb_arg: callback user arg. > * @param rpz_passthru: if true, the rpz passthru was previously found and > * further rpz processing is stopped. > + * @param unique_info: if nonnull, unique info is passed back to be used > + * for the callback remove call. It does not need to be deallocated. > * @return 0 on error. > */ > int mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, > uint16_t qflags, struct edns_data* edns, struct sldns_buffer* buf, > - uint16_t qid, mesh_cb_func_type cb, void* cb_arg, int > rpz_passthru); > + uint16_t qid, mesh_cb_func_type cb, void* cb_arg, int rpz_passthru, > + void** unique_info); > > /** > * New prefetch message. Create new query state if needed. > @@ -544,6 +554,23 @@ struct mesh_state* mesh_area_find(struct > uint16_t qflags, int prime, int valrec); > > /** > + * Find a unique mesh state in the mesh area. Pass relevant flags. > + * > + * @param mesh: the mesh area to look in. > + * @param cinfo: if non-NULL client specific info that may affect IP-based > + * actions that apply to the query result. > + * @param qinfo: what query > + * @param qflags: if RD / CD bit is set or not. > + * @param prime: if it is a priming query. > + * @param valrec: if it is a validation-recursion query. > + * @param unique_info: the unique info for the state. NULL can be passed. > + * @return: mesh state or NULL if not found. > + */ > +struct mesh_state* mesh_area_find_unique(struct mesh_area* mesh, > + struct respip_client_info* cinfo, struct query_info* qinfo, > + uint16_t qflags, int prime, int valrec, void* unique_info); > + > +/** > * Setup attachment super/sub relation between super and sub mesh state. > * The relation must not be present when calling the function. > * Does not update stat items in mesh_area. > @@ -562,11 +589,12 @@ int mesh_state_attachment(struct mesh_st > * @param qid: ID of reply. > * @param qflags: original query flags. > * @param qinfo: original query info. > + * @param result: the allocated reply structure, for rollback. > * @return: 0 on alloc error. > */ > int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, > struct comm_reply* rep, uint16_t qid, uint16_t qflags, > - const struct query_info* qinfo); > + const struct query_info* qinfo, struct mesh_reply** result); > > /** > * Create new callback structure and attach it to a mesh state. > @@ -578,11 +606,12 @@ int mesh_state_add_reply(struct mesh_sta > * @param cb_arg: callback user arg. > * @param qid: ID of reply. > * @param qflags: original query flags. > + * @param result: the allocated callback structure, for rollback. > * @return: 0 on alloc error. > */ > int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns, > struct sldns_buffer* buf, mesh_cb_func_type cb, void* cb_arg, > - uint16_t qid, uint16_t qflags); > + uint16_t qid, uint16_t qflags, struct mesh_cb** result); > > /** > * Run the mesh. Run all runnable mesh states. Which can create new > @@ -683,11 +712,14 @@ void mesh_list_remove(struct mesh_state* > * @param mesh: to update the counters. > * @param m: the mesh state. > * @param cp: the comm_point to remove from the list. > + * @param h2_stream: if not NULL, it specifies the h2_stream to match > + * for the delete. > * @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 doq_stream* doq_stream); > + struct comm_point* cp, struct http2_stream* h2_stream, > + 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. > @@ -734,9 +766,10 @@ void mesh_respond_serve_expired(struct m > * @param qflags: flags from client query. > * @param cb: callback function. > * @param cb_arg: callback user arg. > + * @param unique_info: if not NULL, used to find a unique state for > removal. > */ > void mesh_remove_callback(struct mesh_area* mesh, struct query_info* > qinfo, > - uint16_t qflags, mesh_cb_func_type cb, void* cb_arg); > + uint16_t qflags, mesh_cb_func_type cb, void* cb_arg, void* > unique_info); > > /** Copy the client info to the query region. */ > struct respip_client_info* mesh_copy_client_info(struct regional* region, > Index: services/modstack.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/modstack.c,v > diff -u -p -r1.12 modstack.c > --- services/modstack.c 26 May 2026 11:14:11 -0000 1.12 > +++ services/modstack.c 20 Sep 2026 09:50:48 -0000 > @@ -232,7 +232,7 @@ module_func_block* module_factory(const > return NULL; > } > > -int > +int > modstack_call_startup(struct module_stack* stack, const char* module_conf, > struct module_env* env) > { > @@ -301,7 +301,7 @@ modstack_call_init(struct module_stack* > return 1; > } > > -void > +void > modstack_call_deinit(struct module_stack* stack, struct module_env* env) > { > int i; > @@ -323,7 +323,7 @@ modstack_call_destartup(struct module_st > } > } > > -int > +int > modstack_find(struct module_stack* stack, const char* name) > { > int i; > Index: services/outside_network.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/outside_network.c,v > diff -u -p -r1.35 outside_network.c > --- services/outside_network.c 27 Jul 2026 14:14:39 -0000 1.35 > +++ services/outside_network.c 20 Sep 2026 09:50:48 -0000 > @@ -1702,6 +1702,12 @@ static int setup_if(struct port_if* pif, > !netblockstrtoaddr(addrstr, UNBOUND_DNS_PORT, > &pif->addr, &pif->addrlen, &pif->pfxlen)) > return 0; > +#ifdef INT_MAX > + if(numfd > (size_t)INT_MAX) { > + log_err("num_ports exceeds INT_MAX"); > + return 0; > + } > +#endif > #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION > pif->shpif = shared_ports_find_if(shp, &pif->addr, pif->addrlen, > pif->pfxlen); > @@ -1777,6 +1783,13 @@ outside_network_create(struct comm_base > outside_network_delete(outnet); > return NULL; > } > +#ifdef INT_MAX > + if(num_ports > (size_t)INT_MAX) { > + log_err("outgoing num_ports exceeds INT_MAX"); > + outside_network_delete(outnet); > + return NULL; > + } > +#endif > #ifndef INET6 > do_ip6 = 0; > #endif > @@ -3349,9 +3362,9 @@ serviced_udp_callback(struct comm_point* > if(error == NETEVENT_TIMEOUT) { > if(sq->status == serviced_query_UDP_EDNS && sq->last_rtt < > 5000 && > (serviced_query_udp_size(sq, > serviced_query_UDP_EDNS_FRAG) < serviced_query_udp_size(sq, > serviced_query_UDP_EDNS))) { > - /* fallback to 1480/1280 */ > + /* fallback to 1472/1232 */ > sq->status = serviced_query_UDP_EDNS_FRAG; > - log_name_addr(VERB_ALGO, "try edns1xx0", > sq->qbuf+10, > + log_name_addr(VERB_ALGO, "try edns1xx2", > sq->qbuf+10, > &sq->addr, sq->addrlen); > if(!serviced_udp_send(sq, c->buffer)) { > serviced_callbacks(sq, NETEVENT_CLOSED, c, > rep); > @@ -3488,7 +3501,8 @@ outnet_serviced_query(struct outside_net > char* tls_auth_name, struct sockaddr_storage* addr, socklen_t > addrlen, > uint8_t* zone, size_t zonelen, struct module_qstate* qstate, > comm_point_callback_type* callback, void* callback_arg, > - sldns_buffer* buff, struct module_env* env, int* was_ratelimited) > + sldns_buffer* buff, struct module_env* env, int* was_ratelimited, > + int* ratelimit_incremented) > { > struct serviced_query* sq; > struct service_callback* cb; > @@ -3560,6 +3574,7 @@ outnet_serviced_query(struct outside_net > "delegation point", zone, > LDNS_RR_TYPE_NS, LDNS_RR_CLASS_IN); > } > + *ratelimit_incremented = 1; > } > /* make new serviced query entry */ > sq = serviced_create(outnet, buff, dnssec, want_dnssec, > nocaps, > @@ -3765,7 +3780,33 @@ setup_comm_ssl(struct comm_point* cp, st > (void)SSL_set_tlsext_host_name(cp->ssl, host); > } > #endif > -#ifdef HAVE_SSL_SET1_HOST > +#ifdef HAVE_SSL_SET1_DNSNAME > + if((SSL_CTX_get_verify_mode(outnet->sslctx)&SSL_VERIFY_PEER)) { > + /* because we set SSL_VERIFY_PEER, in netevent in > + * ssl_handshake, it'll check if the certificate > + * verification has succeeded */ > + /* SSL_VERIFY_PEER is set on the sslctx */ > + /* and the certificates to verify with are loaded into > + * it with SSL_load_verify_locations or > + * SSL_CTX_set_default_verify_paths */ > + /* setting the hostname makes openssl verify the > + * host name in the x509 certificate in the > + * SSL connection*/ > + struct sockaddr_storage tmpaddr; > + socklen_t tmpaddrlen = (socklen_t)sizeof(tmpaddr); > + if(ipstrtoaddr(host, UNBOUND_DNS_PORT, &tmpaddr, > &tmpaddrlen)) { > + if(!SSL_set1_ipaddr(cp->ssl, host)) { > + log_err("SSL_set1_ipaddr failed"); > + return 0; > + } > + } else { > + if(!SSL_set1_dnsname(cp->ssl, host)) { > + log_err("SSL_set1_dnsname failed"); > + return 0; > + } > + } > + } > +#elif defined(HAVE_SSL_SET1_HOST) > if((SSL_CTX_get_verify_mode(outnet->sslctx)&SSL_VERIFY_PEER)) { > /* because we set SSL_VERIFY_PEER, in netevent in > * ssl_handshake, it'll check if the certificate > @@ -3894,7 +3935,8 @@ outnet_comm_point_for_http(struct outsid > /* outnet_tcp_connect has closed fd on error for us */ > return 0; > } > - cp = comm_point_create_http_out(outnet->base, 65552, cb, cb_arg, > + cp = comm_point_create_http_out(outnet->base, > + sldns_buffer_capacity(outnet->udp_buff), cb, cb_arg, > outnet->udp_buff); > if(!cp) { > log_err("malloc failure"); > @@ -4085,13 +4127,15 @@ static int shared_ports_alloc_ifs(struct > size_t done_4 = 0, done_6 = 0; > int i; > for(i=0; i - if(str_is_ip6(ifs[i]) && do_ip6) { > + if(str_is_ip6(ifs[i]) && do_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) { > + if(!str_is_ip6(ifs[i]) && do_ip4 && > + (int)done_4 < shp->num_ip4) { > > if(!shared_ports_setup_if(&shp->ip4_ifs[done_4], > ifs[i], availports, numavailports)) > return 0; > @@ -4112,16 +4156,21 @@ struct shared_ports* shared_ports_create > 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; > @@ -4199,6 +4248,9 @@ int shared_ports_fetch_random(struct sha > 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. */ > @@ -4256,6 +4308,9 @@ void shared_ports_return_port(struct sha > #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; > Index: services/outside_network.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/outside_network.h,v > diff -u -p -r1.19 outside_network.h > --- services/outside_network.h 27 Jul 2026 14:14:39 -0000 1.19 > +++ services/outside_network.h 20 Sep 2026 09:50:48 -0000 > @@ -538,7 +538,7 @@ struct serviced_query { > serviced_query_UDP_EDNS_fallback, > /** probe to test TCP noEDNS0 (EDNS gives FORMERRorNOTIMP) > */ > serviced_query_TCP_EDNS_fallback, > - /** send UDP query with EDNS1480 (or 1280) */ > + /** send UDP query with EDNS1472 (or 1232) */ > serviced_query_UDP_EDNS_FRAG > } > /** variable with current status */ > @@ -697,6 +697,8 @@ void pending_delete(struct outside_netwo > * @param env: the module environment. > * @param was_ratelimited: it will signal back if the query failed to > pass the > * ratelimit check. > + * @param ratelimit_incremented: set to true if the ratelimit counter > + * was increased. > * @return 0 on error, or pointer to serviced query that is used to answer > * this serviced query may be shared with other callbacks as well. > */ > @@ -706,7 +708,8 @@ struct serviced_query* outnet_serviced_q > char* tls_auth_name, struct sockaddr_storage* addr, socklen_t > addrlen, > uint8_t* zone, size_t zonelen, struct module_qstate* qstate, > comm_point_callback_type* callback, void* callback_arg, > - struct sldns_buffer* buff, struct module_env* env, int* > was_ratelimited); > + struct sldns_buffer* buff, struct module_env* env, int* > was_ratelimited, > + int* ratelimit_incremented); > > /** > * Remove service query callback. > Index: services/rpz.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/rpz.c,v > diff -u -p -r1.1.1.17 rpz.c > --- services/rpz.c 26 May 2026 11:10:47 -0000 1.1.1.17 > +++ services/rpz.c 20 Sep 2026 09:50:48 -0000 > @@ -721,13 +721,22 @@ rpz_insert_local_zones_trigger(struct lo > char* rrstr = sldns_wire2str_rr(rr, rr_len); > if(rrstr == NULL) { > log_err("malloc error while inserting rpz nsdname > trigger"); > - free(dname); > + if(!newzone) > + free(dname); > lock_rw_unlock(&lz->lock); > return; > } > lock_rw_wrlock(&z->lock); > - local_zone_enter_rr(z, dname, dnamelen, dnamelabs, rrtype, > - rrclass, ttl, rdata, rdata_len, rrstr); > + if(!local_zone_enter_rr(z, dname, dnamelen, dnamelabs, > rrtype, > + rrclass, ttl, rdata, rdata_len, > rrstr)) { > + log_err("rpz: could not enter local-data: %s", > rrstr); > + if(!newzone) > + free(dname); > + lock_rw_unlock(&z->lock); > + lock_rw_unlock(&lz->lock); > + free(rrstr); > + return; > + } > lock_rw_unlock(&z->lock); > free(rrstr); > } > @@ -805,8 +814,9 @@ rpz_insert_nsdname_trigger(struct rpz* r > uint8_t* dname_stripped = NULL; > size_t dnamelen_stripped = 0; > > - rpz_strip_nsdname_suffix(dname, dnamelen, &dname_stripped, > - &dnamelen_stripped); > + if(!rpz_strip_nsdname_suffix(dname, dnamelen, &dname_stripped, > + &dnamelen_stripped)) > + return; > if(a == RPZ_INVALID_ACTION) { > verbose(VERB_ALGO, "rpz: skipping invalid action"); > free(dname_stripped); > @@ -904,8 +914,8 @@ rpz_report_rrset_error(const char* msg, > > /* from localzone.c; difference is we don't have a dname */ > static struct local_rrset* > -rpz_clientip_new_rrset(struct regional* region, > - struct clientip_synthesized_rr* raddr, uint16_t rrtype, uint16_t > rrclass) > +rpz_clientip_new_rrset(struct regional* region, uint16_t rrtype, > + uint16_t rrclass) > { > struct packed_rrset_data* pd; > struct local_rrset* rrset = (struct local_rrset*) > @@ -914,8 +924,6 @@ rpz_clientip_new_rrset(struct regional* > log_err("out of memory"); > return NULL; > } > - rrset->next = raddr->data; > - raddr->data = rrset; > rrset->rrset = (struct ub_packed_rrset_key*) > regional_alloc_zero(region, sizeof(*rrset->rrset)); > if(rrset->rrset == NULL) { > @@ -954,12 +962,18 @@ rpz_clientip_enter_rr(struct regional* r > return 0; > } > > - rrset = rpz_clientip_new_rrset(region, raddr, rrtype, rrclass); > - if(raddr->data == NULL) { > + rrset = rpz_clientip_new_rrset(region, rrtype, rrclass); > + if(rrset == NULL) { > return 0; > } > > - return rrset_insert_rr(region, rrset->rrset->entry.data, rdata, > rdata_len, ttl, ""); > + if(!rrset_insert_rr(region, rrset->rrset->entry.data, rdata, > rdata_len, ttl, "")) > + return 0; > + > + /* Link in now that the allocations have succeeded. */ > + rrset->next = raddr->data; > + raddr->data = rrset; > + return 1; > } > > static int > @@ -982,7 +996,6 @@ rpz_clientip_insert_trigger_rr(struct cl > lock_rw_wrlock(&node->lock); > lock_rw_unlock(&set->lock); > > - node->action = a; > if(a == RPZ_LOCAL_DATA_ACTION) { > if(!rpz_clientip_enter_rr(set->region, node, rrtype, > rrclass, ttl, rdata, rdata_len)) { > @@ -992,6 +1005,7 @@ rpz_clientip_insert_trigger_rr(struct cl > } > > } > + node->action = a; > > lock_rw_unlock(&node->lock); > > @@ -1977,8 +1991,9 @@ rpz_synthesize_nodata(struct rpz* ATTR_U > 0, /* total */ > sec_status_insecure, > LDNS_EDE_NONE); > - if(msg->rep) > - msg->rep->authoritative = 1; > + if(!msg->rep) > + return NULL; > + msg->rep->authoritative = 1; > if(!rpz_add_soa(msg->rep, ms, az)) > return NULL; > return msg; > @@ -2008,8 +2023,9 @@ rpz_synthesize_nxdomain(struct rpz* r, s > 0, /* total */ > sec_status_insecure, > LDNS_EDE_NONE); > - if(msg->rep) > - msg->rep->authoritative = 1; > + if(!msg->rep) > + return NULL; > + msg->rep->authoritative = 1; > if(!rpz_add_soa(msg->rep, ms, az)) > return NULL; > return msg; > Index: services/cache/dns.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/cache/dns.c,v > diff -u -p -r1.29 dns.c > --- services/cache/dns.c 27 Jul 2026 14:14:39 -0000 1.29 > +++ services/cache/dns.c 20 Sep 2026 09:50:48 -0000 > @@ -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" > @@ -131,8 +132,8 @@ store_rrsets(struct module_env* env, str > rep->ref[i].id == rep->ref[i].key->id) { > ttl = ((struct packed_rrset_data*) > rep->rrsets[i]->entry.data)->ttl; > - if(ttl < min_ttl) min_ttl = ttl; > - } > + if(ttl < min_ttl) min_ttl = ttl; > + } > lock_rw_unlock(&rep->ref[i].key->entry.lock); > } > } > @@ -586,8 +587,12 @@ dns_cache_find_delegation(struct module_ > 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; > + } > lock_rw_unlock(&nskey->entry.lock); /* first unlock before next > lookup*/ > /* find and add DS/NSEC (if any) */ > if(msg) > @@ -672,7 +677,7 @@ tomsg(struct module_env* env, struct que > rrset_array_unlock(r->ref, r->rrset_count); > return NULL; > } > - if(r->security == sec_status_secure && > !reply_all_rrsets_secure(r)) { > + if(r->security == sec_status_secure && > !reply_an_ns_rrsets_secure(r)) { > /* message rrsets have changed status, revalidate */ > rrset_array_unlock(r->ref, r->rrset_count); > return NULL; > @@ -782,11 +787,16 @@ synth_dname_msg(struct ub_packed_rrset_k > uint8_t* newname, *dtarg = NULL; > size_t newlen, dtarglen; > time_t rr_ttl; > + int graceperiod = 0; > if(TTL_IS_EXPIRED(d->ttl, now)) { > /* Allow TTL=0 DNAME from upstream within grace period */ > if(!(rrset->rk.flags & PACKED_RRSET_UPSTREAM_0TTL)) > return NULL; > rr_ttl = 0; > + /* Since PACKED_RRSET_UPSTREAM_0TTL set the flag that > + * the grace period has been applied, this stops the rrset > + * from getting stored back into the cache with a bigger > TTL.*/ > + graceperiod = 1; > } else { > rr_ttl = d->ttl - now; > } > @@ -814,6 +824,8 @@ synth_dname_msg(struct ub_packed_rrset_k > msg->rep->rrsets[0] = packed_rrset_copy_region(rrset, region, now); > if(!msg->rep->rrsets[0]) /* copy DNAME */ > return NULL; > + if(graceperiod) > + msg->rep->rrsets[0]->rk.flags |= PACKED_RRSET_0TTL_GRACE; > /* synth CNAME rrset */ > get_cname_target(rrset, &dtarg, &dtarglen); > if(!dtarg) > Index: services/cache/rrset.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/services/cache/rrset.c,v > diff -u -p -r1.12 rrset.c > --- services/cache/rrset.c 27 Jul 2026 14:14:39 -0000 1.12 > +++ services/cache/rrset.c 20 Sep 2026 09:50:48 -0000 > @@ -215,6 +215,13 @@ rrset_cache_update(struct rrset_cache* r > int equal = 0; > log_assert(ref->id != 0 && k->id != 0); > log_assert(k->rk.dname != NULL); > + if((k->rk.flags&PACKED_RRSET_0TTL_GRACE) !=0) { > + log_nametypeclass(VERB_ALGO, "rrset store of > PACKED_RRSET_0TTL_GRACE rrset skipped", k->rk.dname, rrset_type, > ntohs(k->rk.rrset_class)); > + ub_packed_rrset_parsedelete(k, alloc); > + return 0; /* Do not store 0TTL items after apply of > + the grace ttl amount. > + This means the ref was not changed by the call. */ > + } > /* looks up item with a readlock - no editing! */ > if((e=slabhash_lookup(&r->table, h, k, 0)) != 0) { > /* return id and key as they will be used in the cache > @@ -291,6 +298,8 @@ void rrset_cache_update_wildcard(struct > { > struct rrset_ref ref; > uint8_t wc_dname[LDNS_MAX_DOMAINLEN+3]; > + uint8_t* new_dname; > + size_t new_dname_len; > > /* See if the RRSIG signer name allows this wildcard, > * the new rrset should fall within the zone of the RRSIG > signer(s). */ > @@ -310,14 +319,16 @@ void rrset_cache_update_wildcard(struct > wc_dname[1] = (uint8_t)'*'; > memmove(wc_dname+2, ce, ce_len); > > - free(rrset->rk.dname); > - rrset->rk.dname_len = ce_len + 2; > - rrset->rk.dname = (uint8_t*)memdup(wc_dname, rrset->rk.dname_len); > - if(!rrset->rk.dname) { > - alloc_special_release(alloc, rrset); > + new_dname_len = ce_len + 2; > + new_dname = (uint8_t*)memdup(wc_dname, new_dname_len); > + if(!new_dname) { > + ub_packed_rrset_parsedelete(rrset, alloc); > log_err("memdup failure in rrset_cache_update_wildcard"); > return; > } > + free(rrset->rk.dname); > + rrset->rk.dname = new_dname; > + rrset->rk.dname_len = new_dname_len; > > rrset->entry.hash = rrset_key_hash(&rrset->rk); > ref.key = rrset; > Index: sldns/keyraw.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/sldns/keyraw.c,v > diff -u -p -r1.11 keyraw.c > --- sldns/keyraw.c 26 Sep 2025 07:32:37 -0000 1.11 > +++ sldns/keyraw.c 20 Sep 2026 09:50:48 -0000 > @@ -67,19 +67,28 @@ sldns_rr_dnskey_key_size_raw(const unsig > case LDNS_RSASHA512: > #endif > if (len > 0) { > + size_t nlen, offset; > if (keydata[0] == 0) { > /* big exponent */ > if (len > 3) { > memmove(&int16, keydata + 1, 2); > exp = ntohs(int16); > - return (len - exp - 3)*8; > + offset = 3; > } else { > return 0; > } > } else { > exp = keydata[0]; > - return (len-exp-1)*8; > + offset = 1; > } > + if(exp+offset > len) > + return 0; > + nlen = len - exp - offset; > + /* prefixed zeroes mean a smaller value */ > + while(nlen > 0 && > + keydata[len-nlen] == 0) > + nlen--; > + return nlen*8; > } else { > return 0; > } > Index: sldns/str2wire.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/sldns/str2wire.c,v > diff -u -p -r1.19 str2wire.c > --- sldns/str2wire.c 26 Sep 2025 07:32:37 -0000 1.19 > +++ sldns/str2wire.c 20 Sep 2026 09:50:48 -0000 > @@ -842,7 +842,8 @@ rrinternal_parse_rdata(sldns_buffer* str > sldns_write_uint16(rr+dname_len+8, > (uint16_t)(rr_cur_len-dname_len-10)); > *rr_len = rr_cur_len; > /* SVCB/HTTPS handling */ > - if (rr_type == LDNS_RR_TYPE_SVCB || rr_type == LDNS_RR_TYPE_HTTPS) > { > + if ((rr_type == LDNS_RR_TYPE_SVCB || rr_type == LDNS_RR_TYPE_HTTPS) > + && !was_unknown_rr_format) { > size_t rdata_len = rr_cur_len - dname_len - 10; > uint8_t *rdata = rr+dname_len + 10; > > @@ -1201,7 +1202,7 @@ sldns_str2wire_svcbparam_ipv4hint(const > { > size_t count; > char ip_str[INET_ADDRSTRLEN+1]; > - char *next_ip_str; > + const char *next_ip_str; > size_t i; > > for (i = 0, count = 1; val[i]; i++) { > @@ -1256,7 +1257,7 @@ sldns_str2wire_svcbparam_ipv6hint(const > { > size_t count; > char ip_str[INET6_ADDRSTRLEN+1]; > - char *next_ip_str; > + const char *next_ip_str; > size_t i; > > for (i = 0, count = 1; val[i]; i++) { > @@ -1317,7 +1318,7 @@ static int > sldns_str2wire_svcbparam_mandatory(const char* val, uint8_t* rd, size_t* > rd_len) > { > size_t i, count, val_len; > - char* next_key; > + const char* next_key; > > val_len = strlen(val); > > @@ -1410,6 +1411,7 @@ sldns_str2wire_svcbparam_ech_value(const > return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; > sldns_write_uint16(rd, SVCB_KEY_ECH); > sldns_write_uint16(rd + 2, 0); > + *rd_len = 4; > > return LDNS_WIREPARSE_ERR_OK; > } > Index: smallapp/unbound-anchor.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/smallapp/unbound-anchor.c,v > diff -u -p -r1.23 unbound-anchor.c > --- smallapp/unbound-anchor.c 26 May 2026 11:14:11 -0000 1.23 > +++ smallapp/unbound-anchor.c 20 Sep 2026 09:50:48 -0000 > @@ -160,7 +160,7 @@ char* wsa_strerror(int err); > #endif > > static const char ICANN_UPDATE_CA[] = > - /* The ICANN CA fetched at 24 Sep 2010. Valid to 2028 */ > + /* The ICANN CA fetched at 29 May 2026. Valid to 20 Mar 2045 */ > "-----BEGIN CERTIFICATE-----\n" > > "MIIDdzCCAl+gAwIBAgIBATANBgkqhkiG9w0BAQsFADBdMQ4wDAYDVQQKEwVJQ0FO\n" > > "TjEmMCQGA1UECxMdSUNBTk4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNV\n" > @@ -181,6 +181,40 @@ static const char ICANN_UPDATE_CA[] = > > "15nu5JBSewrCkYqYYmaxyOC3WrVGfHZxVI7MpIFcGdvSb2a1uyuua8l0BKgk3ujF\n" > > "0/wsHNeP22qNyVO+XVBzrM8fk8BSUFuiT/6tZTYXRtEt5aKQZgXbKU5dUF3jT9qg\n" > "j/Br5BZw3X/zd325TvnswzMC1+ljLzHnQGGk\n" > + "-----END CERTIFICATE-----\n" > + "\n" > + "-----BEGIN CERTIFICATE-----\n" > + > "MIIFsTCCA5mgAwIBAgIUQFsYkgroBoe69HKQPy8/DQuiLwgwDQYJKoZIhvcNAQEN\n" > + > "BQAwYDELMAkGA1UEBhMCVVMxDjAMBgNVBAoMBUlDQU5OMSYwJAYDVQQLDB1JQ0FO\n" > + > "TiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEZMBcGA1UEAwwQSUNBTk4gUm9vdCBD\n" > + > "QSB2MjAeFw0yNTAzMjAyMTA0MjZaFw00NTAzMjAyMTA0MjZaMGAxCzAJBgNVBAYT\n" > + > "AlVTMQ4wDAYDVQQKDAVJQ0FOTjEmMCQGA1UECwwdSUNBTk4gQ2VydGlmaWNhdGlv\n" > + > "biBBdXRob3JpdHkxGTAXBgNVBAMMEElDQU5OIFJvb3QgQ0EgdjIwggIiMA0GCSqG\n" > + > "SIb3DQEBAQUAA4ICDwAwggIKAoICAQCepDjrubjR7en/uZWo7MAnzFIIvUPYEc7b\n" > + > "+AlefdlEDQ1JEmpfrvt/4CX9lJ9ShIBR6zwrQeDvrj5XZ2kEjbJ8Nnc6sM/ojdyr\n" > + > "5jSLqcDPH9fJg7jCW02KF8CtqWsnqcW6jjTIZcCWkg9lEixdF8QAjIEgJtZte+Yh\n" > + > "XeyN0KD2EaO8U5Id0bLvMyphuO1OCGKzDtetcX8K7SvoshdJx3lPIlYzqXl0nVAY\n" > + > "iCeNdeDzTNjEOHYJOP6dYoZI8nKRJltMkZcCCjBE2vQuSMY2w4pOlWk1skHjMWXj\n" > + > "QsZzngXuNG56zialL0TPEDVWjWRjzOnruHUAs4KUY8Zs+Nt8JdSlXMi825PKoKpp\n" > + > "ESs7/ZG1mPjVOYp7Z7ntrRjJFgnUBjWzVPOx4yHiJj1ur+OpqP18oP5YfqY+tKmz\n" > + > "7vlfRGGOEd08a0XgZISDNKpMAovn5pRUHTWPCCjc28tns9ODPvr1cQi+QSwTv+v8\n" > + > "wnA5etGrsead88Rv/ieaq5ikMJTRDfW4d9SY2uPcMGvfU6VdQLRhQkzEVTQNAJ1R\n" > + > "i2lOoJbbjwnK+OU9OhST/OqdjJDJAhTAstdUnrr8WBU80xM75MIaaTjSBCvZ1wro\n" > + > "pAi2hYb0tedTH6WarSW3MH9HcEoGGzs2GD3hDB0a2eCp+TdAs8Up944SjY7UV4Jx\n" > + > "sOC7TxbmkQIDAQABo2MwYTAdBgNVHQ4EFgQU+1EuMRuOZ/ecsfYzNQ+yGZsxZrMw\n" > + > "HwYDVR0jBBgwFoAU+1EuMRuOZ/ecsfYzNQ+yGZsxZrMwDwYDVR0TAQH/BAUwAwEB\n" > + > "/zAOBgNVHQ8BAf8EBAMCAf4wDQYJKoZIhvcNAQENBQADggIBACz38SkKR1WsEZnX\n" > + > "x1BKaS5/oQPw+7quDQCKGoD2Vz7CR7yQh4zQn/Hh0173vKvRWcwN2io0iLJ1ysv5\n" > + > "jXBLeWZh3djiQlXP3iWp4s01SiUwmFssxi3SD1IT2jNosk1xcVWthle9zth7Y8Mp\n" > + > "iUJYnHobP7tX7H2g+I8Rqw2sEX/yPSYMYcdH5a1xRMPOLHTyOaCgevRBBBtXkiAJ\n" > + > "Ob9QKZTaFaXntPXBKNSGkVb2d+2qKyJMrwd0KNI+SVSoIgNDAxkNOdi9x6X6ETW2\n" > + > "4aYFsytohFVkNUXx2eFYRim4yjnD8PHIvDQSofLfSAC5TOERtwUFd+Mw3/di+HCm\n" > + > "50OJPyoxZLjWQCCfNUZzgZZOe+zT6lgBiV3KB0UuuAdq7jGUeH/328HJDi30BvNj\n" > + > "+TNb9Hmpm+ZDguM+f8p7GxapX8AVNu/xErtl4msYiVJrr1qqV+qLLEMwIz0raujG\n" > + > "FFDd6N43wgduffbU20pThry0Y7rku5+RZjUZe/T7ZL+NUKiqXAPufrkqVkjX/8T+\n" > + > "wyNZz8KkiQwkJthojpppa79FDxn/A2M8tt+FQqIONAUPR2m5nurVgftQH0z5ZtDB\n" > + > "YykUlkUiPOJNXoDOIkbpA7lW2wezeY4te+EiSeUZSE541N5QBwaItaonIZsIgn6C\n" > + "pMnwChV9468oRE20bdqq9+Go7g4E\n" > "-----END CERTIFICATE-----\n"; > > static const char DS_TRUST_ANCHOR[] = > @@ -1678,18 +1712,116 @@ static unsigned long > get_usage_of_ex(X509* cert) > { > unsigned long val = 0; > +#ifdef HAVE_X509_GET_KEY_USAGE > + val = X509_get_key_usage(cert); > + if (val == UINT32_MAX) > + return 0; > +#else > ASN1_BIT_STRING* s; > if((s=X509_get_ext_d2i(cert, NID_key_usage, NULL, NULL))) { > - if(s->length > 0) { > - val = s->data[0]; > - if(s->length > 1) > - val |= s->data[1] << 8; > +# ifdef HAVE_ASN1_STRING_GET0_DATA > + const unsigned char *data = ASN1_STRING_get0_data(s); > +# else > + const unsigned char *data = ASN1_STRING_data(s); > +# endif > + int len = ASN1_STRING_length(s); > + if(len > 0) { > + val = data[0]; > + if(len > 1) > + val |= data[1] << 8; > } > ASN1_BIT_STRING_free(s); > } > +#endif > return val; > } > > +#if !defined(HAVE_X509_NAME_GET_TEXT_BY_NID) || > defined(DEPRECATED_X509_NAME_GET_TEXT_BY_NID) > +/** print verbose output about name extension data. */ > +static void > +print_name_ext( > +#if OPENSSL_VERSION_NUMBER >= 0x40000000 > + const > +#endif > + X509_NAME* nm, int nid, const char* str) > +{ > + int lastpos = -1; > + for(;;) { > +#if OPENSSL_VERSION_NUMBER >= 0x40000000 > + const > +#endif > + X509_NAME_ENTRY* ne; > +#if OPENSSL_VERSION_NUMBER >= 0x40000000 > + const > +#endif > + ASN1_STRING *asn; > + const unsigned char *data; > + char buf[1024]; > + > + lastpos = X509_NAME_get_index_by_NID(nm, nid, lastpos); > + if(lastpos == -1 || lastpos == -2) > + break; > + ne = X509_NAME_get_entry(nm, lastpos); > + if(!ne) continue; > + asn = X509_NAME_ENTRY_get_data(ne); > + if(!asn) continue; > +# ifdef HAVE_ASN1_STRING_GET0_DATA > + data = ASN1_STRING_get0_data(asn); > +# else > + data = ASN1_STRING_data(asn); > +# endif > + if(!data) continue; > + if(ASN1_STRING_length(asn) > (int)sizeof(buf)-1) continue; > + memcpy(buf, data, ASN1_STRING_length(asn)); > + buf[ASN1_STRING_length(asn)]=0; > + printf("%s: %s\n", str, buf); > + } > +} > +#endif /* X509_NAME_GET_TEXT_BY_NID */ > + > +#if !defined(HAVE_X509_NAME_GET_TEXT_BY_NID) || > defined(DEPRECATED_X509_NAME_GET_TEXT_BY_NID) > +/** see if the valid emailaddr is present. */ > +static int > +has_valid_emailaddr( > +#if OPENSSL_VERSION_NUMBER >= 0x40000000 > + const > +#endif > + X509_NAME* nm, const char* p7signer) > +{ > + int lastpos = -1; > + for(;;) { > +#if OPENSSL_VERSION_NUMBER >= 0x40000000 > + const > +#endif > + X509_NAME_ENTRY* ne; > +#if OPENSSL_VERSION_NUMBER >= 0x40000000 > + const > +#endif > + ASN1_STRING *asn; > + const unsigned char *data; > + > + lastpos = X509_NAME_get_index_by_NID(nm, > + NID_pkcs9_emailAddress, lastpos); > + if(lastpos == -1 || lastpos == -2) > + break; > + ne = X509_NAME_get_entry(nm, lastpos); > + if(!ne) continue; > + asn = X509_NAME_ENTRY_get_data(ne); > + if(!asn) continue; > +# ifdef HAVE_ASN1_STRING_GET0_DATA > + data = ASN1_STRING_get0_data(asn); > +# else > + data = ASN1_STRING_data(asn); > +# endif > + if(!data) continue; > + if(ASN1_STRING_length(asn) == (int)strlen(p7signer) && > + strncmp((char*)data, p7signer, strlen(p7signer)) > == 0) > + return 1; /* match */ > + } > + return 0; > +} > +#endif /* X509_NAME_GET_TEXT_BY_NID */ > + > /** get valid signers from the list of signers in the signature */ > static STACK_OF(X509)* > get_valid_signers(PKCS7* p7, const char* p7signer) > @@ -1709,6 +1841,9 @@ get_valid_signers(PKCS7* p7, const char* > return NULL; > } > for(i=0; i +#if OPENSSL_VERSION_NUMBER >= 0x40000000 > + const > +#endif > X509_NAME* nm = X509_get_subject_name( > sk_X509_value(signers, i)); > char buf[1024]; > @@ -1721,17 +1856,29 @@ get_valid_signers(PKCS7* p7, const char* > (int)sizeof(buf)); > printf("signer %d: Subject: %s\n", i, > nmline?nmline:"no subject"); > +#if !defined(HAVE_X509_NAME_GET_TEXT_BY_NID) || > defined(DEPRECATED_X509_NAME_GET_TEXT_BY_NID) > + if(verb >= 3) { > + print_name_ext(nm, NID_commonName, > + "commonName"); > + print_name_ext(nm, NID_pkcs9_emailAddress, > + "emailAddress"); > + } > +#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 > } > if(verb) { > int ku_loc = X509_get_ext_by_NID( > sk_X509_value(signers, i), NID_key_usage, > -1); > if(verb >= 3 && ku_loc >= 0) { > +#if OPENSSL_VERSION_NUMBER >= 0x40000000 > + const > +#endif > X509_EXTENSION *ex = X509_get_ext( > sk_X509_value(signers, i), ku_loc); > if(ex) { > @@ -1745,16 +1892,23 @@ get_valid_signers(PKCS7* p7, const char* > /* there is no name to check, return all records */ > if(verb) printf("did not check commonName of > signer\n"); > } else { > - if(!X509_NAME_get_text_by_NID(nm, > +#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 > emailaddress\n"); > + continue; /* wrong name, skip it */ > + } > +#else > + 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 > } > > /* check that the key usage allows digital signatures > Index: smallapp/unbound-checkconf.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/smallapp/unbound-checkconf.c,v > diff -u -p -r1.28 unbound-checkconf.c > --- smallapp/unbound-checkconf.c 26 May 2026 11:14:11 -0000 > 1.28 > +++ smallapp/unbound-checkconf.c 20 Sep 2026 09:50:48 -0000 > @@ -73,6 +73,9 @@ > #ifdef HAVE_GLOB_H > #include > #endif > +#ifdef HAVE_FNMATCH_H > +#include > +#endif > #ifdef WITH_PYTHONMODULE > #include "pythonmod/pythonmod.h" > #endif > @@ -728,6 +731,122 @@ check_modules_exist(const char* module_c > } > } > > +#ifdef USE_IPSECMOD > +/** Compare filename with string, true if it matches the name. */ > +static int > +file_string_matches(char* str, char* fname, struct config_file* cfg) > +{ > + char* f; > + if(!str || str[0] == 0) > + return 0; > + /* compare name after chroot and working dir are applied */ > + f = fname_after_chroot(str, cfg, 1); > + if(!f) fatal_exit("out of memory"); > + if(strcmp(fname, f) == 0) { > + free(f); > + return 1; > + } > + free(f); > + return 0; > +} > +#endif /* USE_IPSECMOD */ > + > +/** Compare filename with list of files, true if list contains the name. > */ > +static int > +file_list_contains(struct config_strlist* list, char* fname, > + struct config_file* cfg) > +{ > + struct config_strlist* s; > + char* f; > + for(s = list; s; s = s->next) { > + if(!s->str || s->str[0] == 0) > + continue; /* skip if no file name */ > + /* compare names after chroot and working dir are applied > */ > + f = fname_after_chroot(s->str, cfg, 1); > + if(!f) fatal_exit("out of memory"); > + if(strcmp(fname, f) == 0) { > + free(f); > + return 1; > + } > + free(f); > + } > + return 0; > +} > + > +/** Compare filename with list of files, true if list contains the name, > + * with glob compare. */ > +static int > +file_list_contains_wild(struct config_strlist* list, char* fname, > + struct config_file* cfg) > +{ > + struct config_strlist* s; > + char* f; > + for(s = list; s; s = s->next) { > + if(!s->str || s->str[0] == 0) > + continue; /* skip if no file name */ > + /* compare names after chroot and working dir are applied > */ > + f = fname_after_chroot(s->str, cfg, 1); > + if(!f) fatal_exit("out of memory"); > + if(strcmp(fname, f) == 0) { > + free(f); > + return 1; > + } > +#ifdef HAVE_FNMATCH > + if(fnmatch(f, fname, 0) == 0) { > + log_err("trusted-keys-file: \"%s\" matches > zonefile '%s'", > + s->str, fname); > + free(f); > + return 1; > + } > +#endif > + free(f); > + } > + return 0; > +} > + > +/** Check if the auth-zone/rpz zonefile: conflicts with other files, > + * so it would overwrite that file. Refuse it aliasing any read-side > bootstrap > + * file. */ > +static void > +check_file_clobber(struct config_file* cfg) > +{ > + struct config_auth* p; > + char* zfile, *sourceopt = NULL; > + for(p = cfg->auths; p; p = p->next) { > + if(!p->name || p->name[0] == 0) > + continue; /* skip if no name */ > + if(!p->zonefile || p->zonefile[0]==0) > + continue; /* no zone file */ > + zfile = fname_after_chroot(p->zonefile, cfg, 1); > + if(!zfile) fatal_exit("out of memory"); > + if(file_list_contains(cfg->auto_trust_anchor_file_list, > zfile, > + cfg)) > + sourceopt = "auto-trust-anchor-file"; > + else if(file_list_contains(cfg->trust_anchor_file_list, > zfile, > + cfg)) > + sourceopt = "trust-anchor-file"; > + else > if(file_list_contains_wild(cfg->trusted_keys_file_list, > + zfile, cfg)) > + sourceopt = "trusted-keys-file"; > + else if(file_list_contains(cfg->root_hints, zfile, cfg)) > + sourceopt = "root-hints"; > + else > if(file_list_contains(cfg->tls_session_ticket_keys.first, > + zfile, cfg)) > + sourceopt = "tls-session-ticket-keys"; > +#ifdef USE_IPSECMOD > + if(cfg->ipsecmod_enabled && > + file_string_matches(cfg->ipsecmod_hook, zfile, > cfg)) > + sourceopt = "ipsecmod-hook"; > +#endif > + if(sourceopt) > + fatal_exit("auth-zone '%s': zonefile \"%s\" " > + "is the same path as a %s option. " > + "The auth-zone transfer would overwrite > it.", > + p->name, p->zonefile, sourceopt); > + free(zfile); > + } > +} > + > /** check configuration for errors */ > static void > morechecks(struct config_file* cfg) > @@ -822,6 +941,7 @@ morechecks(struct config_file* cfg) > cfg->chrootdir, cfg); > } > #endif > + check_file_clobber(cfg); > /* remove chroot setting so that modules are not stripping > pathnames */ > free(cfg->chrootdir); > cfg->chrootdir = NULL; > Index: smallapp/worker_cb.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/smallapp/worker_cb.c,v > diff -u -p -r1.15 worker_cb.c > --- smallapp/worker_cb.c 27 Jul 2026 14:14:39 -0000 1.15 > +++ smallapp/worker_cb.c 20 Sep 2026 09:50:48 -0000 > @@ -43,6 +43,7 @@ > #include "config.h" > #include "libunbound/context.h" > #include "libunbound/worker.h" > +#include "libunbound/remote.h" > #include "util/fptr_wlist.h" > #include "util/log.h" > #include "services/mesh.h" > @@ -102,7 +103,7 @@ struct outbound_entry* worker_send_query > socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), > size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), int > ATTR_UNUSED(ssl_upstream), > char* ATTR_UNUSED(tls_auth_name), struct module_qstate* > ATTR_UNUSED(q), > - int* ATTR_UNUSED(was_ratelimited)) > + int* ATTR_UNUSED(was_ratelimited), int* > ATTR_UNUSED(ratelimit_incremented)) > { > log_assert(0); > return 0; > @@ -142,7 +143,7 @@ struct outbound_entry* libworker_send_qu > socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), > size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), int > ATTR_UNUSED(ssl_upstream), > char* ATTR_UNUSED(tls_auth_name), struct module_qstate* > ATTR_UNUSED(q), > - int* ATTR_UNUSED(was_ratelimited)) > + int* ATTR_UNUSED(was_ratelimited), int* > ATTR_UNUSED(ratelimit_incremented)) > { > log_assert(0); > return 0; > Index: testcode/dohclient.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/testcode/dohclient.c,v > diff -u -p -r1.1.1.9 dohclient.c > --- testcode/dohclient.c 26 May 2026 11:10:51 -0000 1.1.1.9 > +++ testcode/dohclient.c 20 Sep 2026 09:50:48 -0000 > @@ -146,7 +146,9 @@ submit_query(struct http2_session* h2_se > { > int32_t stream_id; > struct http2_stream* h2_stream; > - nghttp2_nv headers[5]; > + nghttp2_nv headers[6]; > + size_t num_headers = 5; > + char clen[16]; > char* qb64; > size_t qb64_size; > size_t qb64_expected_size; > @@ -194,9 +196,16 @@ submit_query(struct http2_session* h2_se > headers[3].value = (uint8_t*)h2_session->authority; > headers[4].name = (uint8_t*)"content-type"; > headers[4].value = (uint8_t*)h2_session->content_type; > + if(h2_session->post) { > + snprintf(clen, sizeof(clen), "%u", > + (unsigned)sldns_buffer_remaining(buf)); > + headers[5].name = (uint8_t*)"content-length"; > + headers[5].value = (uint8_t*)clen; > + num_headers = 6; > + } > > printf("Request headers\n"); > - for(i=0; i + for(i=0; i headers[i].namelen = strlen((char*)headers[i].name); > headers[i].valuelen = strlen((char*)headers[i].value); > headers[i].flags = NGHTTP2_NV_FLAG_NONE; > @@ -204,7 +213,7 @@ submit_query(struct http2_session* h2_se > } > > stream_id = nghttp2_submit_request(h2_session->session, NULL, > headers, > - sizeof(headers)/sizeof(headers[0]), > + num_headers, > (h2_session->post) ? &data_prd : NULL, h2_stream); > if(stream_id < 0) { > printf("Failed to submit nghttp2 request"); > Index: testcode/doqclient.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/testcode/doqclient.c,v > diff -u -p -r1.1.1.5 doqclient.c > --- testcode/doqclient.c 27 Jul 2026 14:13:39 -0000 1.1.1.5 > +++ testcode/doqclient.c 20 Sep 2026 09:50:48 -0000 > @@ -1137,8 +1137,11 @@ static struct ngtcp2_conn* conn_client_s > client_chosen_version, &cbs, &settings, ¶ms, > NULL, /* ngtcp2_mem allocator, use default */ > data /* callback argument */); > - if(!conn) fatal_exit("could not ngtcp2_conn_client_new: %s", > - ngtcp2_strerror(rv)); > + if(rv!=0) { > + conn = NULL; > + fatal_exit("could not ngtcp2_conn_client_new: %s", > + ngtcp2_strerror(rv)); > + } > data->cc_algo = settings.cc_algo; > return conn; > } > @@ -2098,7 +2101,7 @@ early_data_setup_session(struct doq_clie > SSL_SESSION_free(session); > return 0; > } > -#ifdef USE_NGTCP2_CRYPTO_OSSL > +#ifdef HAVE_SSL_SET_QUIC_TLS_EARLY_DATA_ENABLED > SSL_set_quic_tls_early_data_enabled(data->ssl, 1); > #else > SSL_set_quic_early_data_enabled(data->ssl, 1); > @@ -2595,7 +2598,8 @@ struct outbound_entry* worker_send_query > socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), > size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), > int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), > - struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited)) > + struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited), > + int* ATTR_UNUSED(ratelimit_incremented)) > { > log_assert(0); > return 0; > @@ -2629,7 +2633,8 @@ struct outbound_entry* libworker_send_qu > socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), > size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), > int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), > - struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited)) > + struct module_qstate* ATTR_UNUSED(q), int* > ATTR_UNUSED(was_ratelimited), > + int* ATTR_UNUSED(ratelimit_incremented)) > { > log_assert(0); > return 0; > Index: testcode/fake_event.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/testcode/fake_event.c,v > diff -u -p -r1.1.1.20 fake_event.c > --- testcode/fake_event.c 27 Jul 2026 14:13:40 -0000 1.1.1.20 > +++ testcode/fake_event.c 20 Sep 2026 09:50:48 -0000 > @@ -1276,7 +1276,8 @@ struct serviced_query* outnet_serviced_q > socklen_t addrlen, uint8_t* zone, size_t zonelen, > struct module_qstate* qstate, comm_point_callback_type* callback, > void* callback_arg, sldns_buffer* ATTR_UNUSED(buff), > - struct module_env* env, int* ATTR_UNUSED(was_ratelimited)) > + struct module_env* env, int* ATTR_UNUSED(was_ratelimited), > + int* ATTR_UNUSED(ratelimit_incremented)) > { > struct replay_runtime* runtime = (struct > replay_runtime*)outnet->base; > struct fake_pending* pend = (struct fake_pending*)calloc(1, > Index: testcode/testbound.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/testcode/testbound.c,v > diff -u -p -r1.1.1.14 testbound.c > --- testcode/testbound.c 26 Sep 2025 07:30:48 -0000 1.1.1.14 > +++ testcode/testbound.c 20 Sep 2026 09:50:48 -0000 > @@ -786,3 +786,13 @@ size_t doq_table_quic_size_get(struct do > return 0; > } > #endif > + > +void tcp_read_again_cb(void* ATTR_UNUSED(arg)) > +{ > + /* nothing */ > +} > + > +void tcp_more_read_again_cb(void* ATTR_UNUSED(arg)) > +{ > + /* nothing */ > +} > Index: testcode/unitauth.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/testcode/unitauth.c,v > diff -u -p -r1.1.1.7 unitauth.c > --- testcode/unitauth.c 26 Sep 2025 07:30:47 -0000 1.1.1.7 > +++ testcode/unitauth.c 20 Sep 2026 09:50:48 -0000 > @@ -1027,6 +1027,38 @@ authzone_query_test(void) > check_queries("example.com", zone_example_com, > example_com_queries); > } > > +/** Test chunkline_count_parens output */ > +static void > +authzone_chunkline_count_parens_test(void) > +{ > + sldns_buffer* buf; > + if(vbmp) printf("Testing chunkline_count_parens\n"); > + buf = sldns_buffer_new(1024); > + if(!buf) fatal_exit("out of memory"); > + > + /* Check that escaped characters are handled, '\x', and in quotes. > */ > + sldns_buffer_printf(buf, "TXT \"x\" \\("); > + unit_assert(chunkline_count_parens(buf, 0) == 0); > + > + sldns_buffer_clear(buf); > + sldns_buffer_printf(buf, "TXT ';x' ("); > + unit_assert(chunkline_count_parens(buf, 0) == 0); > + > + sldns_buffer_clear(buf); > + sldns_buffer_printf(buf, "TXT \"a;b\" ("); > + unit_assert(chunkline_count_parens(buf, 0) == 1); > + > + sldns_buffer_clear(buf); > + sldns_buffer_printf(buf, "TXT \\) )"); > + unit_assert(chunkline_count_parens(buf, 0) == -1); > + > + sldns_buffer_clear(buf); > + sldns_buffer_printf(buf, "TXT \"a\\\\\" \"(\" "); > + unit_assert(chunkline_count_parens(buf, 0) == 0); > + > + sldns_buffer_free(buf); > +} > + > /** test authzone code */ > void > authzone_test(void) > @@ -1036,4 +1068,5 @@ authzone_test(void) > authzone_compare_serial(); > authzone_read_test(); > authzone_query_test(); > + authzone_chunkline_count_parens_test(); > } > Index: testcode/unitecs.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/testcode/unitecs.c,v > diff -u -p -r1.1.1.3 unitecs.c > --- testcode/unitecs.c 20 Oct 2022 08:25:17 -0000 1.1.1.3 > +++ testcode/unitecs.c 20 Sep 2026 09:50:48 -0000 > @@ -141,6 +141,7 @@ static addrlen_t randomkey(addrkey_t **k > int bits = rand() % maxlen; > int bytes = bits/8 + (bits%8>0); /*ceil*/ > *k = (addrkey_t *) malloc(bytes * sizeof(addrkey_t)); > + if(!*k) fatal_exit("out of memory"); > for (byte = 0; byte < bytes; byte++) { > (*k)[byte] = (addrkey_t)(rand() & 0xFF); > } > Index: testcode/unitldns.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/testcode/unitldns.c,v > diff -u -p -r1.1.1.7 unitldns.c > --- testcode/unitldns.c 26 May 2026 11:10:51 -0000 1.1.1.7 > +++ testcode/unitldns.c 20 Sep 2026 09:50:48 -0000 > @@ -279,10 +279,24 @@ b64_test(void) > unit_assert(result == -1); > } > > +/** test SVCB ech svcparam */ > +static void > +svcb_ech_test(void) > +{ > + uint8_t rr[LDNS_RR_BUF_SIZE]; > + size_t rr_len = sizeof(rr), dname_len = 0; > + int e = sldns_str2wire_rr_buf("x. 300 IN HTTPS 1 . ech=0", > + rr, &rr_len, &dname_len, 300, NULL, 0, NULL, 0); > + unit_assert(e == LDNS_WIREPARSE_ERR_OK); > + unit_assert(rr_len == dname_len + 10 /* type,class,ttl,rdatalen */ > + 7 /* rdata */); > + unit_assert(sldns_read_uint16(rr + dname_len + 8 /* rdlen */) == > 7); > +} > + > void > ldns_test(void) > { > unit_show_feature("sldns"); > rr_tests(); > b64_test(); > + svcb_ech_test(); > } > Index: testcode/unitmain.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/testcode/unitmain.c,v > diff -u -p -r1.1.1.15 unitmain.c > --- testcode/unitmain.c 27 Jul 2026 14:13:39 -0000 1.1.1.15 > +++ testcode/unitmain.c 20 Sep 2026 09:50:48 -0000 > @@ -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"); > Index: testcode/unitverify.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/testcode/unitverify.c,v > diff -u -p -r1.6 unitverify.c > --- testcode/unitverify.c 26 May 2026 11:14:11 -0000 1.6 > +++ testcode/unitverify.c 20 Sep 2026 09:50:48 -0000 > @@ -196,7 +196,7 @@ verifytest_rrset(struct module_env* env, > setup_sigalg(dnskey, sigalg); /* check all algorithms in the > dnskey */ > /* ok to give null as qstate here, won't be used for answer > section. */ > sec = dnskeyset_verify_rrset(env, ve, rrset, dnskey, sigalg, > &reason, > - NULL, LDNS_SECTION_ANSWER, NULL, &verified, reasonbuf, > + NULL, LDNS_SECTION_ANSWER, NULL, NULL, &verified, > reasonbuf, > sizeof(reasonbuf)); > if(vsig) { > printf("verify outcome is: %s %s\n", > sec_status_to_string(sec), > @@ -510,6 +510,146 @@ nsec3_hash_test(const char* fname) > sldns_buffer_free(buf); > } > > +/** Test the rrset_canonicalize_to_buffer function to see if the > + * size of canon_owner name is properly checked for. */ > +static void > +canon_owner_buf_test(void) > +{ > + struct regional* region; > + sldns_buffer* buf; > + struct ub_packed_rrset_key k; > + struct packed_rrset_data d; > + size_t rr_len[2]; > + time_t rr_ttl[2]; > + uint8_t* rr_data[2]; > + int ret; > + unit_show_func("validator/val_sigcrypt.c", > + "rrset_canonicalize_to_buffer"); > + region = regional_create(); > + if(!region) > + fatal_exit("out of memory"); > + /* Purposefully a very small buffer, to overflow it */ > + buf = sldns_buffer_new(28); > + if(!buf) > + fatal_exit("out of memory"); > + > + /* An RRset to canonicalize. The buffer is made smaller, so > + * it can fail on bounds checks. */ > + memset(&d, 0, sizeof(d)); > + d.ttl = 3600; > + d.count = 1; > + d.rrsig_count = 1; > + d.rr_len = rr_len; > + d.rr_ttl = rr_ttl; > + d.rr_data = rr_data; > + rr_len[0] = 18; > + rr_len[1] = 36; > + rr_ttl[0] = 3600; > + rr_ttl[1] = 3600; > + rr_data[0] = (uint8_t*)"\x00\x10\x0Fzzaaaaaaaaaaaaa"; > + rr_data[1] = > (uint8_t*)"\x00\x24\x00\x06\x08\x3\x01\x02\x03\x04\x01\x02\x03\x04\x01\x02\x03\x04\x12\x34\x03zzz\x00zzaaaaaaaaaaa"; > + > + memset(&k, 0, sizeof(k)); > + k.rk.dname = (uint8_t*) "\x0f" "aaaaaaaaaaaaaaa" "\x00"; > + k.rk.dname_len = 17; > + k.rk.type = htons(LDNS_RR_TYPE_TXT); > + k.rk.rrset_class = htons(LDNS_RR_CLASS_IN); > + k.entry.data = &d; > + > + /* There should be no buffer overflow, assertion failure, here */ > + ret = rrset_canonicalize_to_buffer(region, buf, &k); > + unit_assert(ret == 0); > + > + regional_destroy(region); > + sldns_buffer_free(buf); > +} > + > +/** Test if ds_digest_match_dnskey that calls ds_create_dnskey_digest, > + * checks the buffer size. */ > +static void > +dnskey_ds_digest_test(void) > +{ > + struct regional* region; > + sldns_buffer* buf; > + struct module_env env; > + struct ub_packed_rrset_key k1, k2; > + struct packed_rrset_data d1, d2; > + size_t rr_len1[1], rr_len2[1]; > + time_t rr_ttl1[1], rr_ttl2[1]; > + uint8_t* rr_rdata1[1], *rr_rdata2[1]; > + int ret; > + unit_show_func("validator/val_sigcrypt.c", > "ds_digest_match_dnskey"); > + region = regional_create(); > + if(!region) > + fatal_exit("out of memory"); > + /* Purposefully a very small buffer, to overflow it */ > + buf = sldns_buffer_new(28); > + if(!buf) > + fatal_exit("out of memory"); > + memset(&env, 0, sizeof(env)); > + env.scratch = region; > + env.scratch_buffer = buf; > + > + /* A DNSKEY and DS RRset to match together. The buffer is made > + * smaller, so it can fail on bounds checks. */ > + memset(&d1, 0, sizeof(d1)); > + d1.ttl = 3600; > + d1.count = 1; > + d1.rr_len = rr_len1; > + d1.rr_ttl = rr_ttl1; > + d1.rr_data = rr_rdata1; > + rr_len1[0] = 38; > + rr_ttl1[0] = 3600; > + /* DS rdata has: keytag (2bytes), algorithm (1byte), > + * digesttype (1byte), digest (remainder). */ > + rr_rdata1[0] = (uint8_t*)"\x00\x24" > + "\x12\x34" > + "\x08" /* RSASHA256 */ > + "\x02" /* SHA256 */ > + "0123456789abcdef0123456789abcdef"; /* 32 bytes */ > + ; > + > + memset(&k1, 0, sizeof(k1)); > + k1.rk.dname = (uint8_t*) "\x03" "foo" "\x00"; > + k1.rk.dname_len = 5; > + k1.rk.type = htons(LDNS_RR_TYPE_DS); > + k1.rk.rrset_class = htons(LDNS_RR_CLASS_IN); > + k1.entry.data = &d1; > + > + memset(&d2, 0, sizeof(d2)); > + d2.ttl = 3600; > + d2.count = 1; > + d2.rr_len = rr_len2; > + d2.rr_ttl = rr_ttl2; > + d2.rr_data = rr_rdata2; > + rr_len2[0] = 38; > + rr_ttl2[0] = 3600; > + /* DNSKEY rdata has: flags (2bytes), protocol (1byte), > + * algorithm (1byte), publickey (remainder). */ > + rr_rdata2[0] = (uint8_t*)"\x00\x24" > + "\x01\x01" /* KSK */ > + "\x03" /* DNSSEC_KEYPROTO */ > + "\x08" /* RSASHA256 */ > + "0123456789abcdef0123456789abcdef"; /* 32 bytes of content > */ > + ; > + > + memset(&k2, 0, sizeof(k2)); > + k2.rk.dname = (uint8_t*) "\x03" "foo" "\x00"; > + k2.rk.dname_len = 5; > + k2.rk.type = htons(LDNS_RR_TYPE_DNSKEY); > + k2.rk.rrset_class = htons(LDNS_RR_CLASS_IN); > + k2.entry.data = &d2; > + /* 36 byte rdata length for DNSKEY (38-2), and dname length of 5, > + * exceeds the (small) buffer size. */ > + > + /* There should be no buffer overflow, assertion failure, here */ > + ret = ds_digest_match_dnskey(&env, &k2, 0, &k1, 0); > + unit_assert(ret == 0); > + > + regional_destroy(region); > + sldns_buffer_free(buf); > +} > + > #define xstr(s) str(s) > #define str(s) #s > > @@ -724,4 +864,6 @@ verify_test(void) > #endif > nsectest(); > nsec3_hash_test(SRCDIRSTR "/testdata/test_nsec3_hash.1"); > + dnskey_ds_digest_test(); > + canon_owner_buf_test(); > } > Index: util/config_file.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/config_file.c,v > diff -u -p -r1.42 config_file.c > --- util/config_file.c 26 May 2026 11:14:11 -0000 1.42 > +++ util/config_file.c 20 Sep 2026 09:50:48 -0000 > @@ -46,6 +46,7 @@ > #ifdef HAVE_TIME_H > #include > #endif > +#include > #include "util/log.h" > #include "util/configyyrename.h" > #include "util/config_file.h" > @@ -93,7 +94,7 @@ struct config_parser_state* cfg_parser = > static void init_outgoing_availports(int* array, int num); > > /** init cookie with random data */ > -static void init_cookie_secret(uint8_t* cookie_secret, size_t > cookie_secret_len); > +static int init_cookie_secret(struct config_file* cfg); > > struct config_file* > config_create(void) > @@ -277,7 +278,7 @@ config_create(void) > cfg->val_sig_skew_min = 3600; /* at least daylight savings trouble > */ > cfg->val_sig_skew_max = 86400; /* at most timezone settings > trouble */ > cfg->val_max_restart = 5; > - cfg->val_clean_additional = 1; > + cfg->val_clean_additional = 0; /* off to protect against much > data. */ > cfg->val_log_level = 0; > cfg->val_log_squelch = 0; > cfg->val_permissive_mode = 0; > @@ -389,8 +390,7 @@ config_create(void) > #endif > cfg->do_answer_cookie = 0; > memset(cfg->cookie_secret, 0, sizeof(cfg->cookie_secret)); > - cfg->cookie_secret_len = 16; > - init_cookie_secret(cfg->cookie_secret, cfg->cookie_secret_len); > + cfg->cookie_secret_len = 0; /* not set yet */ > cfg->cookie_secret_file = NULL; > #ifdef USE_CACHEDB > if(!(cfg->cachedb_backend = strdup("testframe"))) goto error_exit; > @@ -429,6 +429,8 @@ config_create(void) > cfg->iter_scrub_rrsig = 8; > cfg->iter_scrub_promiscuous = 1; > cfg->max_global_quota = 200; > + cfg->val_validation_attempts = 32; > + cfg->val_hash_attempts = 32; > return cfg; > error_exit: > config_delete(cfg); > @@ -533,7 +535,11 @@ probe_maxrto(int useful_server_top_timeo > int config_apply_max_rtt(int max_rtt) > { > USEFUL_SERVER_TOP_TIMEOUT = max_rtt; > - BLACKLIST_PENALTY = max_rtt*4; > + BLACKLIST_PENALTY = > +#ifdef INT_MAX > + (max_rtt > INT_MAX/4) ? INT_MAX : > +#endif > + max_rtt*4; > PROBE_MAXRTO = probe_maxrto(max_rtt); > return max_rtt; > } > @@ -776,11 +782,13 @@ int config_set_option(struct config_file > else S_YNO("ede:", ede) > else S_YNO("ede-serve-expired:", ede_serve_expired) > else S_YNO("dns-error-reporting:", dns_error_reporting) > - else S_NUMBER_OR_ZERO("iter-scrub-ns:", iter_scrub_ns) > + else S_NUMBER_NONZERO("iter-scrub-ns:", iter_scrub_ns) > else S_NUMBER_OR_ZERO("iter-scrub-cname:", iter_scrub_cname) > else S_NUMBER_OR_ZERO("iter-scrub-rrsig:", iter_scrub_rrsig) > else S_YNO("iter-scrub-promiscuous:", iter_scrub_promiscuous) > else S_NUMBER_OR_ZERO("max-global-quota:", max_global_quota) > + else S_NUMBER_OR_ZERO("val-validation-attempts:", > val_validation_attempts) > + else S_NUMBER_OR_ZERO("val-hash-attempts:", val_hash_attempts) > else S_YNO("serve-original-ttl:", serve_original_ttl) > else S_STR("val-nsec3-keysize-iterations:", > val_nsec3_key_iterations) > else S_YNO("zonemd-permissive-mode:", zonemd_permissive_mode) > @@ -1261,6 +1269,8 @@ config_get_option(struct config_file* cf > else O_DEC(opt, "iter-scrub-rrsig", iter_scrub_rrsig) > else O_YNO(opt, "iter-scrub-promiscuous", iter_scrub_promiscuous) > else O_DEC(opt, "max-global-quota", max_global_quota) > + else O_DEC(opt, "val-validation-attempts", val_validation_attempts) > + else O_DEC(opt, "val-hash-attempts", val_hash_attempts) > else O_YNO(opt, "serve-original-ttl", serve_original_ttl) > else O_STR(opt, > "val-nsec3-keysize-iterations",val_nsec3_key_iterations) > else O_YNO(opt, "zonemd-permissive-mode", zonemd_permissive_mode) > @@ -1572,6 +1582,8 @@ config_read(struct config_file* cfg, con > } > globfree(&g); > config_auto_slab_values(cfg); > + if(!init_cookie_secret(cfg)) > + return 0; > return 1; > } > #endif /* HAVE_GLOB */ > @@ -1596,6 +1608,8 @@ config_read(struct config_file* cfg, con > } > > config_auto_slab_values(cfg); > + if(!init_cookie_secret(cfg)) > + return 0; > return 1; > } > > @@ -1870,18 +1884,33 @@ config_delete(struct config_file* cfg) > free(cfg); > } > > -static void > -init_cookie_secret(uint8_t* cookie_secret, size_t cookie_secret_len) > +static int > +init_cookie_secret(struct config_file* cfg) > { > - struct ub_randstate *rand = ub_initstate(NULL); > + struct ub_randstate* rand; > + size_t cookie_secret_len; > + uint8_t* cookie_secret; > + if(!cfg->do_answer_cookie) > + return 1; > + if(cfg->cookie_secret_file && cfg->cookie_secret_file[0]) > + return 1; > + if(cfg->cookie_secret_len != 0) > + return 1; > > - if (!rand) > - fatal_exit("could not init random generator"); > + rand = ub_initstate(NULL); > + if(!rand) { > + log_err("init_cookie_secret: could not init random > generator"); > + return 0; > + } > + cfg->cookie_secret_len = 16; > + cookie_secret_len = cfg->cookie_secret_len; > + cookie_secret = cfg->cookie_secret; > while (cookie_secret_len) { > *cookie_secret++ = (uint8_t)ub_random(rand); > cookie_secret_len--; > } > ub_randfree(rand); > + return 1; > } > > static void > @@ -1944,7 +1973,7 @@ extract_port_from_str(const char* str, i > int > cfg_mark_ports(const char* str, int allow, int* avail, int num) > { > - char* mid = strchr(str, '-'); > + const char* mid = strchr(str, '-'); > #ifdef DISABLE_EXPLICIT_PORT_RANDOMISATION > log_warn("Explicit port randomisation disabled, ignoring " > "outgoing-port-permit and outgoing-port-avoid > configuration " > @@ -1952,7 +1981,7 @@ cfg_mark_ports(const char* str, int allo > #endif > if(!mid) { > int port = extract_port_from_str(str, num); > - if(port < 0) { > + if (port < 0) { > log_err("Failed to parse the port number"); > return 0; > } > @@ -1962,7 +1991,7 @@ cfg_mark_ports(const char* str, int allo > char buf[16]; > int i, low; > int high = extract_port_from_str(mid+1, num); > - if(high < 0) { > + if (high < 0) { > log_err("Failed to parse the port number"); > return 0; > } > @@ -1976,7 +2005,7 @@ cfg_mark_ports(const char* str, int allo > memcpy(buf, str, (size_t)(mid-str)); > buf[mid-str] = 0; > low = extract_port_from_str(buf, num); > - if(low < 0) { > + if (low < 0) { > log_err("Failed to parse the port number"); > return 0; > } > @@ -2647,10 +2676,10 @@ fname_after_chroot(const char* fname, st > } > > /** return next space character in string */ > -static char* next_space_pos(const char* str) > +static const char* next_space_pos(const char* str) > { > - char* sp = strchr(str, ' '); > - char* tab = strchr(str, '\t'); > + const char* sp = strchr(str, ' '); > + const char* tab = strchr(str, '\t'); > if(!tab && !sp) > return NULL; > if(!sp) return tab; > @@ -2659,10 +2688,10 @@ static char* next_space_pos(const char* > } > > /** return last space character in string */ > -static char* last_space_pos(const char* str) > +static const char* last_space_pos(const char* str) > { > - char* sp = strrchr(str, ' '); > - char* tab = strrchr(str, '\t'); > + const char* sp = strrchr(str, ' '); > + const char* tab = strrchr(str, '\t'); > if(!tab && !sp) > return NULL; > if(!sp) return tab; > @@ -2720,8 +2749,8 @@ cfg_parse_local_zone(struct config_file* > > char* cfg_ptr_reverse(char* str) > { > - char* ip, *ip_end; > - char* name; > + const char* ip, *ip_end; > + const char* name; > char* result; > char buf[1024]; > struct sockaddr_storage addr; > @@ -2872,7 +2901,7 @@ if_listens_on(const char* ifname, int de > struct config_strlist* additional_ports) > { > struct config_strlist* s; > - char* p = strchr(ifname, '@'); > + const char* p = strchr(ifname, '@'); > int if_port; > if(p) if_port = atoi(p+1); > else if_port = default_port; > Index: util/config_file.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/config_file.h,v > diff -u -p -r1.39 config_file.h > --- util/config_file.h 26 May 2026 11:14:11 -0000 1.39 > +++ util/config_file.h 20 Sep 2026 09:50:48 -0000 > @@ -798,6 +798,10 @@ struct config_file { > int iter_scrub_rrsig; > /** limit on upstream queries for an incoming query and > subqueries. */ > int max_global_quota; > + /** limit on validator validation attempts. */ > + int val_validation_attempts; > + /** limit on validator hash attempts. */ > + int val_hash_attempts; > /** Should the iterator scrub promiscuous NS rrsets, from positive > * answers. */ > int iter_scrub_promiscuous; > @@ -884,6 +888,10 @@ struct config_auth { > int zonemd_check; > /** Reject absence of ZONEMD records, zone must have one */ > int zonemd_reject_absence; > + /** The maximum auth zone transfer size, in bytes. */ > + size_t max_transfer_size; > + /** The maximum auth zone transfer time taken, in msec. */ > + int max_transfer_time; > }; > > /** > Index: util/configlexer.lex > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/configlexer.lex,v > diff -u -p -r1.35 configlexer.lex > --- util/configlexer.lex 26 May 2026 11:14:11 -0000 1.35 > +++ util/configlexer.lex 20 Sep 2026 09:50:48 -0000 > @@ -608,6 +608,10 @@ iter-scrub-ns{COLON} { YDVAR(1, VAR_ITE > iter-scrub-cname{COLON} { YDVAR(1, VAR_ITER_SCRUB_CNAME) } > iter-scrub-rrsig{COLON} { YDVAR(1, VAR_ITER_SCRUB_RRSIG) } > max-global-quota{COLON} { YDVAR(1, VAR_MAX_GLOBAL_QUOTA) } > +val-validation-attempts{COLON} { YDVAR(1, VAR_VAL_VALIDATION_ATTEMPTS) } > +val-hash-attempts{COLON} { YDVAR(1, VAR_VAL_HASH_ATTEMPTS) } > +max-transfer-size{COLON} { YDVAR(1, VAR_MAX_TRANSFER_SIZE) } > +max-transfer-time{COLON} { YDVAR(1, VAR_MAX_TRANSFER_TIME) } > iter-scrub-promiscuous{COLON} { YDVAR(1, VAR_ITER_SCRUB_PROMISCUOUS) } > {NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++; } > > Index: util/configparser.y > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/configparser.y,v > diff -u -p -r1.37 configparser.y > --- util/configparser.y 26 May 2026 11:14:11 -0000 1.37 > +++ util/configparser.y 20 Sep 2026 09:50:48 -0000 > @@ -216,7 +216,9 @@ extern struct config_parser_state* cfg_p > %token VAR_LOG_DESTADDR VAR_CACHEDB_CHECK_WHEN_SERVE_EXPIRED > %token VAR_COOKIE_SECRET_FILE VAR_ITER_SCRUB_NS VAR_ITER_SCRUB_CNAME > %token VAR_ITER_SCRUB_RRSIG > +%token VAR_MAX_TRANSFER_SIZE VAR_MAX_TRANSFER_TIME > %token VAR_MAX_GLOBAL_QUOTA VAR_HARDEN_UNVERIFIED_GLUE VAR_LOG_TIME_ISO > +%token VAR_VAL_VALIDATION_ATTEMPTS VAR_VAL_HASH_ATTEMPTS > %token VAR_ITER_SCRUB_PROMISCUOUS VAR_LOG_THREAD_ID > > %% > @@ -359,7 +361,8 @@ content_server: server_num_threads | ser > server_harden_unknown_additional | server_disable_edns_do | > server_log_destaddr | server_cookie_secret_file | > server_iter_scrub_ns | server_iter_scrub_cname | > server_max_global_quota | > - server_iter_scrub_rrsig | > + server_val_validation_attempts | > + server_val_hash_attempts | server_iter_scrub_rrsig | > server_harden_unverified_glue | server_log_time_iso | > server_iter_scrub_promiscuous > ; > stub_clause: stubstart contents_stub > @@ -459,6 +462,8 @@ authstart: VAR_AUTH_ZONE > s->zonemd_check = 0; > s->zonemd_reject_absence = 0; > s->isrpz = 0; > + s->max_transfer_size = 0; > + s->max_transfer_time = 0; > } else { > yyerror("out of memory"); > } > @@ -468,7 +473,8 @@ contents_auth: contents_auth content_aut > | ; > content_auth: auth_name | auth_zonefile | auth_master | auth_url | > auth_for_downstream | auth_for_upstream | auth_fallback_enabled | > - auth_allow_notify | auth_zonemd_check | auth_zonemd_reject_absence > + auth_allow_notify | auth_zonemd_check | auth_zonemd_reject_absence > | > + auth_max_transfer_size | auth_max_transfer_time > ; > > rpz_tag: VAR_TAGS STRING_ARG > @@ -556,6 +562,8 @@ rpzstart: VAR_RPZ > s->for_upstream = 0; > s->fallback_enabled = 0; > s->isrpz = 1; > + s->max_transfer_size = 0; > + s->max_transfer_time = 0; > } else { > yyerror("out of memory"); > } > @@ -565,7 +573,8 @@ contents_rpz: contents_rpz content_rpz > | ; > content_rpz: auth_name | auth_zonefile | rpz_tag | auth_master | auth_url > | > auth_allow_notify | rpz_action_override | rpz_cname_override | > - rpz_log | rpz_log_name | rpz_signal_nxdomain_ra | > auth_for_downstream > + rpz_log | rpz_log_name | rpz_signal_nxdomain_ra | > auth_for_downstream | > + auth_max_transfer_size | auth_max_transfer_time > ; > server_num_threads: VAR_NUM_THREADS STRING_ARG > { > @@ -657,7 +666,7 @@ server_send_client_subnet: VAR_SEND_CLIE > #ifdef CLIENT_SUBNET > OUTYY(("P(server_send_client_subnet:%s)\n", $2)); > if(!cfg_strlist_insert(&cfg_parser->cfg->client_subnet, > $2)) > - fatal_exit("out of memory adding client-subnet"); > + yyerror("out of memory"); > #else > OUTYY(("P(Compiled without edns subnet option, > ignoring)\n")); > free($2); > @@ -670,7 +679,7 @@ server_client_subnet_zone: VAR_CLIENT_SU > OUTYY(("P(server_client_subnet_zone:%s)\n", $2)); > > if(!cfg_strlist_insert(&cfg_parser->cfg->client_subnet_zone, > $2)) > - fatal_exit("out of memory adding > client-subnet-zone"); > + yyerror("out of memory"); > #else > OUTYY(("P(Compiled without edns subnet option, > ignoring)\n")); > free($2); > @@ -2029,7 +2038,7 @@ server_access_control: VAR_ACCESS_CONTRO > OUTYY(("P(server_access_control:%s %s)\n", $2, $3)); > validate_acl_action($3); > if(!cfg_str2list_insert(&cfg_parser->cfg->acls, $2, $3)) > - fatal_exit("out of memory adding acl"); > + yyerror("out of memory"); > } > ; > server_interface_action: VAR_INTERFACE_ACTION STRING_ARG STRING_ARG > @@ -2038,7 +2047,7 @@ server_interface_action: VAR_INTERFACE_A > validate_acl_action($3); > if(!cfg_str2list_insert( > &cfg_parser->cfg->interface_actions, $2, $3)) > - fatal_exit("out of memory adding acl"); > + yyerror("out of memory"); > } > ; > server_module_conf: VAR_MODULE_CONF STRING_ARG > @@ -2388,6 +2397,9 @@ server_local_zone: VAR_LOCAL_ZONE STRING > && strcmp($3, "typetransparent")!=0 > && 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 > @@ -2400,7 +2412,9 @@ server_local_zone: VAR_LOCAL_ZONE STRING > 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, " > + "block_a_wdata, block_aaaa_wdata, " > "always_refuse, always_nxdomain, " > "always_nodata, always_deny, always_null, " > "noview, nodefault or ipset"); > @@ -2409,7 +2423,7 @@ server_local_zone: VAR_LOCAL_ZONE STRING > } else if(strcmp($3, "nodefault")==0) { > if(!cfg_strlist_insert(&cfg_parser->cfg-> > local_zones_nodefault, $2)) > - fatal_exit("out of memory adding > local-zone"); > + yyerror("out of memory"); > free($3); > #ifdef USE_IPSET > } else if(strcmp($3, "ipset")==0) { > @@ -2417,21 +2431,24 @@ server_local_zone: VAR_LOCAL_ZONE STRING > /* Make sure to add the trailing dot. > * These are str compared to domain names. */ > if($2[len-1] != '.') { > + char* prev = $2; > if(!($2 = realloc($2, len+2))) { > - fatal_exit("out of memory adding > local-zone"); > + yyerror("out of memory"); > + free(prev); > + } else { > + $2[len] = '.'; > + $2[len+1] = 0; > } > - $2[len] = '.'; > - $2[len+1] = 0; > } > if(!cfg_strlist_insert(&cfg_parser->cfg-> > local_zones_ipset, $2)) > - fatal_exit("out of memory adding > local-zone"); > + yyerror("out of memory"); > free($3); > #endif > } else { > > if(!cfg_str2list_insert(&cfg_parser->cfg->local_zones, > $2, $3)) > - fatal_exit("out of memory adding > local-zone"); > + yyerror("out of memory"); > } > } > ; > @@ -2439,7 +2456,7 @@ server_local_data: VAR_LOCAL_DATA STRING > { > OUTYY(("P(server_local_data:%s)\n", $2)); > if(!cfg_strlist_insert(&cfg_parser->cfg->local_data, $2)) > - fatal_exit("out of memory adding local-data"); > + yyerror("out of memory"); > } > ; > server_local_data_ptr: VAR_LOCAL_DATA_PTR STRING_ARG > @@ -2451,7 +2468,7 @@ server_local_data_ptr: VAR_LOCAL_DATA_PT > if(ptr) { > if(!cfg_strlist_insert(&cfg_parser->cfg-> > local_data, ptr)) > - fatal_exit("out of memory adding > local-data"); > + yyerror("out of memory"); > } else { > yyerror("local-data-ptr could not be reversed"); > } > @@ -2515,8 +2532,7 @@ server_wait_limit_netblock: VAR_WAIT_LIM > } else { > if(!cfg_str2list_insert(&cfg_parser->cfg-> > wait_limit_netblock, $2, $3)) > - fatal_exit("out of memory adding " > - "wait-limit-netblock"); > + yyerror("out of memory"); > } > } > ; > @@ -2530,8 +2546,7 @@ server_wait_limit_cookie_netblock: VAR_W > } else { > if(!cfg_str2list_insert(&cfg_parser->cfg-> > wait_limit_cookie_netblock, $2, $3)) > - fatal_exit("out of memory adding " > - "wait-limit-cookie-netblock"); > + yyerror("out of memory"); > } > } > ; > @@ -2563,7 +2578,7 @@ server_dns64_ignore_aaaa: VAR_DNS64_IGNO > OUTYY(("P(dns64_ignore_aaaa:%s)\n", $2)); > if(!cfg_strlist_insert(&cfg_parser->cfg->dns64_ignore_aaaa, > $2)) > - fatal_exit("out of memory adding > dns64-ignore-aaaa"); > + yyerror("out of memory"); > } > ; > server_nat64_prefix: VAR_NAT64_PREFIX STRING_ARG > @@ -2828,8 +2843,7 @@ server_ratelimit_for_domain: VAR_RATELIM > } else { > if(!cfg_str2list_insert(&cfg_parser->cfg-> > ratelimit_for_domain, $2, $3)) > - fatal_exit("out of memory adding " > - "ratelimit-for-domain"); > + yyerror("out of memory"); > } > } > ; > @@ -2843,8 +2857,7 @@ server_ratelimit_below_domain: VAR_RATEL > } else { > if(!cfg_str2list_insert(&cfg_parser->cfg-> > ratelimit_below_domain, $2, $3)) > - fatal_exit("out of memory adding " > - "ratelimit-below-domain"); > + yyerror("out of memory"); > } > } > ; > @@ -3078,8 +3091,7 @@ server_edns_client_string: VAR_EDNS_CLIE > OUTYY(("P(server_edns_client_string:%s %s)\n", $2, $3)); > if(!cfg_str2list_insert( > &cfg_parser->cfg->edns_client_strings, $2, $3)) > - fatal_exit("out of memory adding " > - "edns-client-string"); > + yyerror("out of memory"); > } > ; > server_edns_client_string_opcode: VAR_EDNS_CLIENT_STRING_OPCODE STRING_ARG > @@ -3341,6 +3353,23 @@ auth_fallback_enabled: VAR_FALLBACK_ENAB > free($2); > } > ; > +auth_max_transfer_size: VAR_MAX_TRANSFER_SIZE STRING_ARG > + { > + OUTYY(("P(max-transfer-size:%s)\n", $2)); > + if(!cfg_parse_memsize($2, > &cfg_parser->cfg->auths->max_transfer_size)) > + yyerror("memory size expected"); > + free($2); > + } > + ; > +auth_max_transfer_time: VAR_MAX_TRANSFER_TIME STRING_ARG > + { > + OUTYY(("P(max-transfer-time:%s)\n", $2)); > + if(atoi($2) == 0 && strcmp($2, "0") != 0) > + yyerror("number expected"); > + else cfg_parser->cfg->auths->max_transfer_time = atoi($2); > + free($2); > + } > + ; > view_name: VAR_NAME STRING_ARG > { > OUTYY(("P(name:%s)\n", $2)); > @@ -3380,7 +3409,7 @@ view_local_zone: VAR_LOCAL_ZONE STRING_A > } else if(strcmp($3, "nodefault")==0) { > if(!cfg_strlist_insert(&cfg_parser->cfg->views-> > local_zones_nodefault, $2)) > - fatal_exit("out of memory adding > local-zone"); > + yyerror("out of memory"); > free($3); > #ifdef USE_IPSET > } else if(strcmp($3, "ipset")==0) { > @@ -3388,22 +3417,25 @@ view_local_zone: VAR_LOCAL_ZONE STRING_A > /* Make sure to add the trailing dot. > * These are str compared to domain names. */ > if($2[len-1] != '.') { > + char* prev = $2; > if(!($2 = realloc($2, len+2))) { > - fatal_exit("out of memory adding > local-zone"); > + yyerror("out of memory"); > + free(prev); > + } else { > + $2[len] = '.'; > + $2[len+1] = 0; > } > - $2[len] = '.'; > - $2[len+1] = 0; > } > if(!cfg_strlist_insert(&cfg_parser->cfg->views-> > local_zones_ipset, $2)) > - fatal_exit("out of memory adding > local-zone"); > + yyerror("out of memory"); > free($3); > #endif > } else { > if(!cfg_str2list_insert( > &cfg_parser->cfg->views->local_zones, > $2, $3)) > - fatal_exit("out of memory adding > local-zone"); > + yyerror("out of memory"); > } > } > ; > @@ -3413,8 +3445,7 @@ view_response_ip: VAR_RESPONSE_IP STRING > validate_respip_action($3); > if(!cfg_str2list_insert( > &cfg_parser->cfg->views->respip_actions, $2, $3)) > - fatal_exit("out of memory adding per-view " > - "response-ip action"); > + yyerror("out of memory"); > } > ; > view_response_ip_data: VAR_RESPONSE_IP_DATA STRING_ARG STRING_ARG > @@ -3422,14 +3453,14 @@ view_response_ip_data: VAR_RESPONSE_IP_D > OUTYY(("P(view_response_ip_data:%s)\n", $2)); > if(!cfg_str2list_insert( > &cfg_parser->cfg->views->respip_data, $2, $3)) > - fatal_exit("out of memory adding > response-ip-data"); > + yyerror("out of memory"); > } > ; > view_local_data: VAR_LOCAL_DATA STRING_ARG > { > OUTYY(("P(view_local_data:%s)\n", $2)); > > if(!cfg_strlist_insert(&cfg_parser->cfg->views->local_data, $2)) { > - fatal_exit("out of memory adding local-data"); > + yyerror("out of memory"); > } > } > ; > @@ -3442,7 +3473,7 @@ view_local_data_ptr: VAR_LOCAL_DATA_PTR > if(ptr) { > if(!cfg_strlist_insert(&cfg_parser->cfg->views-> > local_data, ptr)) > - fatal_exit("out of memory adding > local-data"); > + yyerror("out of memory"); > } else { > yyerror("local-data-ptr could not be reversed"); > } > @@ -3782,7 +3813,7 @@ server_response_ip: VAR_RESPONSE_IP STRI > validate_respip_action($3); > if(!cfg_str2list_insert(&cfg_parser->cfg->respip_actions, > $2, $3)) > - fatal_exit("out of memory adding response-ip"); > + yyerror("out of memory"); > } > ; > server_response_ip_data: VAR_RESPONSE_IP_DATA STRING_ARG STRING_ARG > @@ -3790,7 +3821,7 @@ server_response_ip_data: VAR_RESPONSE_IP > OUTYY(("P(server_response_ip_data:%s)\n", $2)); > if(!cfg_str2list_insert(&cfg_parser->cfg->respip_data, > $2, $3)) > - fatal_exit("out of memory adding > response-ip-data"); > + yyerror("out of memory"); > } > ; > dnscstart: VAR_DNSCRYPT > @@ -3838,26 +3869,30 @@ dnsc_dnscrypt_provider: VAR_DNSCRYPT_PRO > dnsc_dnscrypt_provider_cert: VAR_DNSCRYPT_PROVIDER_CERT STRING_ARG > { > OUTYY(("P(dnsc_dnscrypt_provider_cert:%s)\n", $2)); > - > if(cfg_strlist_find(cfg_parser->cfg->dnscrypt_provider_cert, $2)) > + > if(cfg_strlist_find(cfg_parser->cfg->dnscrypt_provider_cert, $2)) { > log_warn("dnscrypt-provider-cert %s is a > duplicate", $2); > - > if(!cfg_strlist_insert(&cfg_parser->cfg->dnscrypt_provider_cert, $2)) > - fatal_exit("out of memory adding > dnscrypt-provider-cert"); > + free($2); > + } else > if(!cfg_strlist_insert(&cfg_parser->cfg->dnscrypt_provider_cert, $2)) { > + yyerror("out of memory"); > + } > } > ; > dnsc_dnscrypt_provider_cert_rotated: VAR_DNSCRYPT_PROVIDER_CERT_ROTATED > STRING_ARG > { > OUTYY(("P(dnsc_dnscrypt_provider_cert_rotated:%s)\n", $2)); > > if(!cfg_strlist_insert(&cfg_parser->cfg->dnscrypt_provider_cert_rotated, > $2)) > - fatal_exit("out of memory adding > dnscrypt-provider-cert-rotated"); > + yyerror("out of memory"); > } > ; > dnsc_dnscrypt_secret_key: VAR_DNSCRYPT_SECRET_KEY STRING_ARG > { > OUTYY(("P(dnsc_dnscrypt_secret_key:%s)\n", $2)); > - if(cfg_strlist_find(cfg_parser->cfg->dnscrypt_secret_key, > $2)) > + if(cfg_strlist_find(cfg_parser->cfg->dnscrypt_secret_key, > $2)) { > log_warn("dnscrypt-secret-key: %s is a duplicate", > $2); > - > if(!cfg_strlist_insert(&cfg_parser->cfg->dnscrypt_secret_key, $2)) > - fatal_exit("out of memory adding > dnscrypt-secret-key"); > + free($2); > + } else > if(!cfg_strlist_insert(&cfg_parser->cfg->dnscrypt_secret_key, $2)) { > + yyerror("out of memory"); > + } > } > ; > dnsc_dnscrypt_shared_secret_cache_size: > VAR_DNSCRYPT_SHARED_SECRET_CACHE_SIZE STRING_ARG > @@ -4202,7 +4237,7 @@ server_tcp_connection_limit: VAR_TCP_CON > yyerror("positive number expected"); > else { > > if(!cfg_str2list_insert(&cfg_parser->cfg->tcp_connection_limits, $2, $3)) > - fatal_exit("out of memory adding tcp > connection limit"); > + yyerror("out of memory"); > } > } > ; > @@ -4241,8 +4276,8 @@ server_cookie_secret_file: VAR_COOKIE_SE > server_iter_scrub_ns: VAR_ITER_SCRUB_NS STRING_ARG > { > OUTYY(("P(server_iter_scrub_ns:%s)\n", $2)); > - if(atoi($2) == 0 && strcmp($2, "0") != 0) > - yyerror("number expected"); > + if(atoi($2) < 1) > + yyerror("number >= 1 expected"); > else cfg_parser->cfg->iter_scrub_ns = atoi($2); > free($2); > } > @@ -4281,6 +4316,24 @@ server_iter_scrub_promiscuous: VAR_ITER_ > yyerror("expected yes or no."); > else cfg_parser->cfg->iter_scrub_promiscuous = > (strcmp($2, "yes")==0); > + free($2); > + } > + ; > +server_val_validation_attempts: VAR_VAL_VALIDATION_ATTEMPTS STRING_ARG > + { > + OUTYY(("P(server_val_validation_attempts:%s)\n", $2)); > + if(atoi($2) == 0 && strcmp($2, "0") != 0) > + yyerror("number expected"); > + else cfg_parser->cfg->val_validation_attempts = atoi($2); > + free($2); > + } > + ; > +server_val_hash_attempts: VAR_VAL_HASH_ATTEMPTS STRING_ARG > + { > + OUTYY(("P(server_val_hash_attempts:%s)\n", $2)); > + if(atoi($2) == 0 && strcmp($2, "0") != 0) > + yyerror("number expected"); > + else cfg_parser->cfg->val_hash_attempts = atoi($2); > free($2); > } > ; > Index: util/fptr_wlist.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/fptr_wlist.c,v > diff -u -p -r1.31 fptr_wlist.c > --- util/fptr_wlist.c 27 Jul 2026 14:14:39 -0000 1.31 > +++ util/fptr_wlist.c 20 Sep 2026 09:50:48 -0000 > @@ -141,6 +141,8 @@ fptr_whitelist_comm_timer(void (*fptr)(v > #ifdef UB_ON_WINDOWS > else if(fptr == &wsvc_cron_cb) return 1; > #endif > + else if(fptr == &tcp_read_again_cb) return 1; > + else if(fptr == &tcp_more_read_again_cb) return 1; > else if(fptr == &auth_xfer_timer) return 1; > else if(fptr == &auth_xfer_probe_timer_callback) return 1; > else if(fptr == &auth_xfer_transfer_timer_callback) return 1; > @@ -362,7 +364,7 @@ fptr_whitelist_modenv_send_query(struct > int nocaps, int check_ratelimit, struct sockaddr_storage* addr, > socklen_t addrlen, uint8_t* zone, size_t zonelen, int tcp_upstream, > int ssl_upstream, char* tls_auth_name, struct module_qstate* q, > - int* was_ratelimited)) > + int* was_ratelimited, int* ratelimit_incremented)) > { > if(fptr == &worker_send_query) return 1; > else if(fptr == &libworker_send_query) return 1; > @@ -413,7 +415,7 @@ fptr_whitelist_modenv_detect_cycle(int ( > return 0; > } > > -int > +int > fptr_whitelist_mod_init(int (*fptr)(struct module_env* env, int id)) > { > if(fptr == &iter_init) return 1; > @@ -441,7 +443,7 @@ fptr_whitelist_mod_init(int (*fptr)(stru > return 0; > } > > -int > +int > fptr_whitelist_mod_deinit(void (*fptr)(struct module_env* env, int id)) > { > if(fptr == &iter_deinit) return 1; > Index: util/fptr_wlist.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/fptr_wlist.h,v > diff -u -p -r1.13 fptr_wlist.h > --- util/fptr_wlist.h 26 May 2026 11:14:11 -0000 1.13 > +++ util/fptr_wlist.h 20 Sep 2026 09:50:48 -0000 > @@ -214,7 +214,7 @@ int fptr_whitelist_modenv_send_query(str > int nocaps, int check_ratelimit, struct sockaddr_storage* addr, > socklen_t addrlen, uint8_t* zone, size_t zonelen, int tcp_upstream, > int ssl_upstream, char* tls_auth_name, struct module_qstate* q, > - int* was_ratelimited)); > + int* was_ratelimited, int* ratelimit_incremented)); > > /** > * Check function pointer whitelist for module_env detach_subs callback > values. > Index: util/iana_ports.inc > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/iana_ports.inc,v > diff -u -p -r1.31 iana_ports.inc > --- util/iana_ports.inc 26 May 2026 11:14:11 -0000 1.31 > +++ util/iana_ports.inc 20 Sep 2026 09:50:48 -0000 > @@ -3866,6 +3866,7 @@ > 4456, > 4457, > 4458, > +4480, > 4484, > 4486, > 4488, > @@ -4506,6 +4507,7 @@ > 6581, > 6582, > 6583, > +6610, > 6619, > 6620, > 6621, > @@ -4608,6 +4610,7 @@ > 7101, > 7107, > 7121, > +7123, > 7128, > 7129, > 7161, > @@ -5393,6 +5396,7 @@ > 30004, > 30260, > 30832, > +30939, > 30999, > 31016, > 31029, > Index: util/module.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/module.h,v > diff -u -p -r1.23 module.h > --- util/module.h 27 Jul 2026 14:14:39 -0000 1.23 > +++ util/module.h 20 Sep 2026 09:50:48 -0000 > @@ -375,6 +375,8 @@ struct module_env { > * @param q: which query state to reactivate upon return. > * @param was_ratelimited: it will signal back if the query failed > to pass the > * ratelimit check. > + * @param ratelimit_incremented: set to true if the ratelimit > counter > + * was increased. > * @return: false on failure (memory or socket related). no query > was > * sent. Or returns an outbound entry with qsent and qstate > set. > * This outbound_entry will be used on later module > invocations > @@ -385,7 +387,8 @@ struct module_env { > int check_ratelimit, > struct sockaddr_storage* addr, socklen_t addrlen, > uint8_t* zone, size_t zonelen, int tcp_upstream, int > ssl_upstream, > - char* tls_auth_name, struct module_qstate* q, int* > was_ratelimited); > + char* tls_auth_name, struct module_qstate* q, int* > was_ratelimited, > + int* ratelimit_incremented); > > /** > * Detach-subqueries. > @@ -698,10 +701,16 @@ struct module_qstate { > time_t qstarttime; > /** whether a message from cachedb will be used for the reply */ > int is_cachedb_answer; > + /** whether the reply is subnet specific */ > + int is_subnet_answer; > /** if the response as error is from error_response_cache, and is > * suitable for caching (briefly) the error response. Set by the > * iterator when no_cache_store is enabled, and there is an error. > */ > int error_response_cache; > + /** if the iterator sees that the forward/stub has no_cache set. > + * to signal to calling modules that their setting of no_cache for > + * other reasons, has to take into account the fwd/stub no_cache. > */ > + int fwd_stub_no_cache; > > /** > * Attributes of clients that share the qstate that may affect > IP-based > @@ -736,7 +745,7 @@ struct module_func_block { > /** text string name of module */ > const char* name; > > - /** > + /** > * Set up the module for start. This is called only once at > startup. > * Privileged operations like opening device files may be done > here. > * The function ptr can be NULL, if it is not used. > Index: util/net_help.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/net_help.c,v > diff -u -p -r1.36 net_help.c > --- util/net_help.c 26 May 2026 11:14:11 -0000 1.36 > +++ util/net_help.c 20 Sep 2026 09:50:48 -0000 > @@ -242,7 +242,7 @@ int > extstrtoaddr(const char* str, struct sockaddr_storage* addr, > socklen_t* addrlen, int port) > { > - char* s; > + const char* s; > if((s=strchr(str, '@'))) { > char buf[MAX_ADDR_STRLEN]; > if(s-str >= MAX_ADDR_STRLEN) { > @@ -268,7 +268,7 @@ ipstrtoaddr(const char* ip, int port, st > p = (uint16_t) port; > if(str_is_ip6(ip)) { > char buf[MAX_ADDR_STRLEN]; > - char* s; > + const char* s; > struct sockaddr_in6* sa = (struct sockaddr_in6*)addr; > *addrlen = (socklen_t)sizeof(struct sockaddr_in6); > memset(sa, 0, *addrlen); > @@ -304,8 +304,9 @@ ipstrtoaddr(const char* ip, int port, st > int netblockstrtoaddr(const char* str, int port, struct sockaddr_storage* > addr, > socklen_t* addrlen, int* net) > { > + const char* s; > char buf[64]; > - char* s; > + char* b = NULL; > *net = (str_is_ip6(str)?128:32); > if((s=strchr(str, '/'))) { > if(atoi(s+1) > *net) { > @@ -323,15 +324,15 @@ int netblockstrtoaddr(const char* str, i > return 0; > } > strlcpy(buf, str, sizeof(buf)); > - s = strchr(buf, '/'); > - if(s) *s = 0; > - s = buf; > + b = strchr(buf, '/'); > + if(b) *b = 0; > + b = buf; > } > - if(!ipstrtoaddr(s?s:str, port, addr, addrlen)) { > + if(!ipstrtoaddr(b?b:str, port, addr, addrlen)) { > log_err("cannot parse ip address: '%s'", str); > return 0; > } > - if(s) { > + if(b) { > addr_mask(addr, *addrlen, *net); > } > return 1; > @@ -1445,6 +1446,8 @@ void* listen_sslctx_create(const char* k > SSL_CTX_set_alpn_select_cb(ctx, doh_alpn_select_cb, NULL); > #endif > } > +#else /* HAVE_SSL_CTX_SET_ALPN_SELECT_CB */ > + (void)is_dot; (void)is_doh; > #endif /* HAVE_SSL_CTX_SET_ALPN_SELECT_CB */ > return ctx; > #else > @@ -1704,6 +1707,10 @@ int check_auth_name_for_ssl(char* auth_n > /** set the authname on an SSL structure, SSL* ssl */ > int set_auth_name_on_ssl(void* ssl, char* auth_name, int use_sni) > { > +#ifdef HAVE_SSL_SET1_DNSNAME > + struct sockaddr_storage tmpaddr; > + socklen_t tmpaddrlen = (socklen_t)sizeof(tmpaddr); > +#endif > if(!auth_name) return 1; > #ifdef HAVE_SSL > if(use_sni) { > @@ -1713,7 +1720,20 @@ int set_auth_name_on_ssl(void* ssl, char > (void)ssl; > (void)use_sni; > #endif > -#ifdef HAVE_SSL_SET1_HOST > +#ifdef HAVE_SSL_SET1_DNSNAME > + SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL); > + if(ipstrtoaddr(auth_name, UNBOUND_DNS_PORT, &tmpaddr, > &tmpaddrlen)) { > + if(!SSL_set1_ipaddr(ssl, auth_name)) { > + log_err("SSL_set1_ipaddr failed"); > + return 0; > + } > + } else { > + if(!SSL_set1_dnsname(ssl, auth_name)) { > + log_err("SSL_set1_dnsname failed"); > + return 0; > + } > + } > +#elif defined(HAVE_SSL_SET1_HOST) > SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL); > /* setting the hostname makes openssl verify the > * host name in the x509 certificate in the > Index: util/netevent.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/netevent.c,v > diff -u -p -r1.44 netevent.c > --- util/netevent.c 27 Jul 2026 14:14:39 -0000 1.44 > +++ util/netevent.c 20 Sep 2026 09:50:48 -0000 > @@ -122,6 +122,10 @@ > #define NUM_UDP_PER_SELECT 1 > #endif > > +/** The number of TCP queries over a TCP connection, per read indication > + * from select. */ > +#define NUM_TCP_PER_SELECT 100 > + > /** timeout in millisec to wait for write to unblock, packets dropped > after.*/ > #define SEND_BLOCKED_WAIT_TIMEOUT 200 > /** max number of times to wait for write to unblock, packets dropped > after.*/ > @@ -951,6 +955,10 @@ static int consume_pp2_header(struct sld > { > struct sockaddr_in* addr = > (struct sockaddr_in*)&rep->client_addr; > + if(ntohs(header->len) < PP2_HEADER_LEN_INET) { > + verbose(VERB_OPS, "proxy_protocol: header > too short for IPv4 address"); > + return 0; > + } > addr->sin_family = AF_INET; > addr->sin_addr.s_addr = > header->addr.addr4.src_addr; > addr->sin_port = header->addr.addr4.src_port; > @@ -963,6 +971,10 @@ static int consume_pp2_header(struct sld > { > struct sockaddr_in6* addr = > (struct sockaddr_in6*)&rep->client_addr; > + if(ntohs(header->len) < PP2_HEADER_LEN_INET6) { > + verbose(VERB_OPS, "proxy_protocol: header > too short for IPv6 address"); > + return 0; > + } > memset(addr, 0, sizeof(*addr)); > addr->sin6_family = AF_INET6; > memcpy(&addr->sin6_addr, > @@ -2932,6 +2944,8 @@ setup_tcp_handler(struct comm_point* c, > c->tcp_is_reading = 1; > c->tcp_byte_count = 0; > c->tcp_keepalive = 0; > + /* reset to configured value before applying load-based reduction > */ > + c->tcp_timeout_msec = c->tcp_parent->tcp_timeout_msec; > /* if more than half the tcp handlers are in use, use a shorter > * timeout for this TCP connection, we need to make space for > * other connections to be able to get attention */ > @@ -2967,6 +2981,62 @@ void comm_base_handle_slow_accept(int AT > } > } > > +/** out of resources in the accept path: pause all listening for > + * NETEVENT_SLOW_ACCEPT_TIME and re-arm via comm_base_handle_slow_accept. > + * > + * If the routine fails, the socket is accepted and then closed, draining > it > + * from the waiting list of connections to be accepted. > + * @param c: the comm point that is a listening socket. > + * @param msec: if 0: uses the slow accept time. Otherwise, sets the time > + * to wait. > + */ > +static void > +comm_point_slow_accept(struct comm_point* c, int msec) > +{ > + struct comm_base* b = c->ev->base; > + struct timeval tv; > + struct ub_event* slowev; > + if(!b->stop_accept) > + return; > + if(b->eb->slow_accept_enabled) > + return; > + /* Allocate the event */ > + slowev = ub_event_new(b->eb->base, -1, UB_EV_TIMEOUT, > + comm_base_handle_slow_accept, b); > + if(!slowev) { > + /* The slow accept was not enabled yet, to handle > + * the allocation failure, instead drain the incoming > + * connection. */ > + int new_fd = accept(c->fd, NULL, NULL); > + if(new_fd != -1) { > + verbose(VERB_ALGO, "slow accept: event_new failed, > " > + "drop connection"); > + sock_close(new_fd); > + } > + return; > + } > + ub_comm_base_now(b); > + if(b->eb->last_slow_log+SLOW_LOG_TIME <= b->eb->secs) { > + b->eb->last_slow_log = b->eb->secs; > + verbose(VERB_OPS, "out of resources on accept, " > + "slow down accept for %d msec", > + NETEVENT_SLOW_ACCEPT_TIME); > + } > + b->eb->slow_accept_enabled = 1; > + fptr_ok(fptr_whitelist_stop_accept(b->stop_accept)); > + (*b->stop_accept)(b->cb_arg); > + /* set timeout, no mallocs */ > + if(msec == 0) > + msec = NETEVENT_SLOW_ACCEPT_TIME; > + tv.tv_sec = msec/1000; > + tv.tv_usec = (msec%1000)*1000; > + b->eb->slow_accept = slowev; > + if(ub_event_add(b->eb->slow_accept, &tv) != 0) { > + /* we do not want to log here, > + * error: "event_add failed." */ > + } > +} > + > int comm_point_perform_accept(struct comm_point* c, > struct sockaddr_storage* addr, socklen_t* addrlen) > { > @@ -3000,6 +3070,14 @@ int comm_point_perform_accept(struct com > if(c->ev->base->stop_accept) { > struct comm_base* b = c->ev->base; > struct timeval tv; > + struct ub_event* slowev = ub_event_new( > + b->eb->base, -1, UB_EV_TIMEOUT, > + comm_base_handle_slow_accept, b); > + if(!slowev) { > + verbose(VERB_ALGO, "slow accept: " > + "event_new failed"); > + return -1; > + } > verbose(VERB_ALGO, "out of file > descriptors: " > "slow accept"); > ub_comm_base_now(b); > @@ -3019,15 +3097,8 @@ int comm_point_perform_accept(struct com > /* set timeout, no mallocs */ > tv.tv_sec = NETEVENT_SLOW_ACCEPT_TIME/1000; > tv.tv_usec = > (NETEVENT_SLOW_ACCEPT_TIME%1000)*1000; > - b->eb->slow_accept = > ub_event_new(b->eb->base, > - -1, UB_EV_TIMEOUT, > - comm_base_handle_slow_accept, b); > - if(b->eb->slow_accept == NULL) { > - /* we do not want to log here, > because > - * that would spam the logfiles. > - * error: "event_base_set failed." > */ > - } > - else if(ub_event_add(b->eb->slow_accept, > &tv) > + b->eb->slow_accept = slowev; > + if(ub_event_add(b->eb->slow_accept, &tv) > != 0) { > /* we do not want to log here, > * error: "event_add failed." */ > @@ -3159,6 +3230,26 @@ static int http2_submit_settings(struct > } > #endif /* HAVE_NGHTTP2 */ > > +/** Clear http2 stream mesh states */ > +static void http2_session_clear_meshstate(struct http2_session* > h2_session) > +{ > +#ifdef HAVE_NGHTTP2 > + /* Since the session gets closed, remove the mesh state > references. */ > + struct http2_stream* h2_stream; > + for(h2_stream = h2_session->first_stream; h2_stream; > + h2_stream = h2_stream->next) { > + if(h2_stream->mesh_state) { > + mesh_state_remove_reply(h2_stream->mesh, > + h2_stream->mesh_state, h2_session->c, > + h2_stream, NULL); > + h2_stream->mesh_state = NULL; > + } > + } > +#else > + (void)h2_session; > +#endif /* HAVE_NGHTTP2 */ > +} > + > #ifdef HAVE_NGHTTP2 > /** Delete http2 stream. After session delete or stream close callback */ > static void http2_stream_delete(struct http2_session* h2_session, > @@ -3166,7 +3257,7 @@ static void http2_stream_delete(struct h > { > if(h2_stream->mesh_state) { > mesh_state_remove_reply(h2_stream->mesh, > h2_stream->mesh_state, > - h2_session->c, NULL); > + h2_session->c, h2_stream, NULL); > h2_stream->mesh_state = NULL; > } > http2_req_stream_clear(h2_stream); > @@ -3208,6 +3299,13 @@ comm_point_tcp_accept_callback(int fd, s > /* find free tcp handler. */ > if(!c->tcp_free) { > log_warn("accepted too many tcp, connections full"); > + /* Wait for a short moment (say 50msec) so that other > + * TCP connections can complete. Or timeout, at the busy > + * timeout of about 200msec. That stops this routine from > + * spinning endlessly, and gives time to complete the other > + * requests. But it is not as slow as the 2000msec wait > + * time for when the kernel is out of buffers. */ > + comm_point_slow_accept(c, NETEVENT_SLOW_ACCEPT_QUEUE_TIME); > return; > } > /* accept incoming connection. */ > @@ -3229,6 +3327,7 @@ comm_point_tcp_accept_callback(int fd, s > if(!c_hdl->h2_session || > !http2_session_server_create(c_hdl->h2_session)) { > log_warn("failed to create nghttp2"); > + comm_point_slow_accept(c, 0); > return; > } > if(!c_hdl->h2_session || > @@ -3236,6 +3335,7 @@ comm_point_tcp_accept_callback(int fd, s > log_warn("failed to submit http2 settings"); > if(c_hdl->h2_session) > > http2_session_server_delete(c_hdl->h2_session); > + comm_point_slow_accept(c, 0); > return; > } > if(!c->ssl) { > @@ -3252,11 +3352,12 @@ comm_point_tcp_accept_callback(int fd, s > comm_point_tcp_handle_callback, c_hdl); > } > if(!c_hdl->ev->ev) { > - log_warn("could not ub_event_new, dropped tcp"); > + log_warn("could not ub_event_new, for new tcp"); > #ifdef HAVE_NGHTTP2 > if(c_hdl->type == comm_http && c_hdl->h2_session) > http2_session_server_delete(c_hdl->h2_session); > #endif > + comm_point_slow_accept(c, 0); > return; > } > log_assert(fd != -1); > @@ -3270,6 +3371,10 @@ comm_point_tcp_accept_callback(int fd, s > #endif > return; > } > + /* move per-netblock TCP-connection-limit handle to the handler so > that > + * comm_point_close() on the handler decrements the count on close > */ > + c_hdl->tcl_addr = c->tcl_addr; > + c->tcl_addr = NULL; > /* Copy remote_address to client_address. > * Simplest way/time for streams to do that. */ > c_hdl->repinfo.client_addrlen = c_hdl->repinfo.remote_addrlen; > @@ -4172,8 +4277,8 @@ recv_error: > if(errno == EINTR || errno == EAGAIN) > return 1; > #ifdef ECONNRESET > - if(errno == ECONNRESET && verbosity < 2) > - return 0; /* silence reset by peer */ > + if(errno == ECONNRESET && verbosity < 2) > + return 0; /* silence reset by peer */ > #endif > if(recv_initial) { > #ifdef ECONNREFUSED > @@ -4540,6 +4645,10 @@ comm_point_tcp_handle_write(int fd, stru > static int > tcp_req_info_read_again(int fd, struct comm_point* c) > { > + /* One event-loop visit drains at most this many pipelined queries; > + * the rest is re-queued, so that other file descriptors get > + * serviced in between. */ > + int budget = NUM_TCP_PER_SELECT; > while(c->tcp_req_info->read_again) { > int r; > c->tcp_req_info->read_again = 0; > @@ -4556,6 +4665,16 @@ tcp_req_info_read_again(int fd, struct c > } > return 0; > } > + if(--budget <= 0 && c->tcp_req_info->read_again) { > + /* Defer the rest of the drain to the next loop > turn. > + * This uses a zero delay timer. For TLS the > undrained > + * remainder sits in OpenSSL's user-space buffer. > */ > + struct timeval tv; > + memset(&tv, 0, sizeof(tv)); > + verbose(VERB_ALGO, "Defer tcp_req_info read > again"); > + comm_timer_set(c->tcp_req_info->read_again_timer, > &tv); > + return 1; > + } > } > return 1; > } > @@ -4569,6 +4688,7 @@ tcp_more_read_again(int fd, struct comm_ > /* this continues until the read routines get EAGAIN or so, > * and thus does not call the callback, and the bool is 0 */ > int* moreread = c->tcp_more_read_again; > + int budget = NUM_TCP_PER_SELECT; > while(moreread && *moreread) { > *moreread = 0; > if(!comm_point_tcp_handle_read(fd, c, 0)) { > @@ -4581,6 +4701,30 @@ tcp_more_read_again(int fd, struct comm_ > } > return; > } > + if(--budget <= 0 && *moreread) { > + /* Defer the rest of the drain to the next loop > turn. > + * This uses a zero delay timer. For TLS the > undrained > + * remainder sits in OpenSSL's user-space buffer. > */ > + struct timeval tv; > + memset(&tv, 0, sizeof(tv)); > + if(!c->tcp_more_read_again_timer) { > + c->tcp_more_read_again_timer = > comm_timer_create(c->ev->base, tcp_more_read_again_cb, c); > + if(!c->tcp_more_read_again_timer) { > + log_err("out of memory for tcp > more read again timer"); > + reclaim_tcp_handler(c); > + if(!c->tcp_do_close) { > + > fptr_ok(fptr_whitelist_comm_point( > + c->callback)); > + (void)(*c->callback)(c, > c->cb_arg, > + NETEVENT_CLOSED, > NULL); > + } > + return; > + } > + } > + verbose(VERB_ALGO, "Defer more read again"); > + comm_timer_set(c->tcp_more_read_again_timer, &tv); > + return; > + } > } > } > > @@ -4609,6 +4753,23 @@ tcp_more_write_again(int fd, struct comm > } > > void > +tcp_read_again_cb(void* arg) > +{ > + struct tcp_req_info* req = (struct tcp_req_info*)arg; > + verbose(VERB_ALGO, "tcp_read_again_cb"); > + if(!tcp_req_info_read_again(req->cp->fd, req->cp)) > + return; > +} > + > +void > +tcp_more_read_again_cb(void* arg) > +{ > + struct comm_point* c = (struct comm_point*)arg; > + verbose(VERB_ALGO, "tcp_more_read_again_cb"); > + tcp_more_read_again(c->fd, c); > +} > + > +void > comm_point_tcp_handle_callback(int fd, short event, void* arg) > { > struct comm_point* c = (struct comm_point*)arg; > @@ -5014,6 +5175,14 @@ http_chunked_segment(struct comm_point* > c->http_stored = 0; > sldns_buffer_skip(c->buffer, (ssize_t)c->tcp_byte_count); > sldns_buffer_clear(c->http_temp); > + if(sldns_buffer_remaining(c->buffer) > > + sldns_buffer_capacity(c->http_temp)) { > + verbose(VERB_OPS, "http chunked: surplus %d > exceeds " > + "temp buffer %d", > (int)sldns_buffer_remaining( > + c->buffer), (int)sldns_buffer_capacity( > + c->http_temp)); > + return 0; > + } > sldns_buffer_write(c->http_temp, > sldns_buffer_current(c->buffer), > sldns_buffer_remaining(c->buffer)); > @@ -5344,6 +5513,13 @@ comm_point_http_handle_read(int fd, stru > if(c->http_in_headers || c->http_in_chunk_headers) { > /* if header is done, process the header */ > if(!http_header_done(c->buffer)) { > + if(sldns_buffer_limit(c->buffer) == > + sldns_buffer_capacity(c->buffer)) { > + verbose(VERB_OPS, "http header > line " > + "exceeds %d bytes, > transfer " > + "failed", > (int)sldns_buffer_capacity(c->buffer)); > + return 0; > + } > /* copy remaining data to front of buffer > * and set rest for writing into it */ > http_moveover_buffer(c->buffer); > @@ -6035,7 +6211,7 @@ comm_point_create_tcp_handler(struct com > c->pp2_enabled = parent->pp2_enabled; > c->pp2_header_state = pp2_header_none; > if(spoolbuf) { > - c->tcp_req_info = tcp_req_info_create(spoolbuf); > + c->tcp_req_info = tcp_req_info_create(base, spoolbuf); > if(!c->tcp_req_info) { > log_err("could not create tcp commpoint"); > sldns_buffer_free(c->buffer); > @@ -6584,7 +6760,10 @@ comm_point_close(struct comm_point* c) > c->event_added = 0; > } > } > - tcl_close_connection(c->tcl_addr); > + if(c->tcl_addr) { > + tcl_close_connection(c->tcl_addr); > + c->tcl_addr = NULL; > + } > if(c->tcp_req_info) > tcp_req_info_clear(c->tcp_req_info); > if(c->h2_session) > @@ -6594,6 +6773,9 @@ comm_point_close(struct comm_point* c) > *c->tcp_more_read_again = 0; > if(c->tcp_more_write_again && *c->tcp_more_write_again) > *c->tcp_more_write_again = 0; > + if(c->tcp_more_read_again_timer && > + comm_timer_is_set(c->tcp_more_read_again_timer)) > + comm_timer_disable(c->tcp_more_read_again_timer); > > /* close fd after removing from event lists, or epoll.. is messed > up */ > if(c->fd != -1 && !c->do_not_close) { > @@ -6633,6 +6815,7 @@ comm_point_delete(struct comm_point* c) > free(c->tcp_handlers); > } > free(c->timeout); > + comm_timer_delete(c->tcp_more_read_again_timer); > if(c->type == comm_tcp || c->type == comm_local || c->type == > comm_http) { > sldns_buffer_free(c->buffer); > #ifdef USE_DNSCRYPT > @@ -6773,6 +6956,7 @@ comm_point_drop_reply(struct comm_reply* > if(repinfo->c->type == comm_http) { > if(repinfo->c->h2_session) { > repinfo->c->h2_session->is_drop = 1; > + > http2_session_clear_meshstate(repinfo->c->h2_session); > if(!repinfo->c->h2_session->postpone_drop) > reclaim_http_handler(repinfo->c); > return; > Index: util/netevent.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/netevent.h,v > diff -u -p -r1.27 netevent.h > --- util/netevent.h 27 Jul 2026 14:14:39 -0000 1.27 > +++ util/netevent.h 20 Sep 2026 09:50:48 -0000 > @@ -111,6 +111,8 @@ typedef int comm_point_callback_type(str > > /** timeout to slow accept calls when not possible, in msec. */ > #define NETEVENT_SLOW_ACCEPT_TIME 2000 > +/** timeout to slow accept calls when tcp queue is full, in msec. */ > +#define NETEVENT_SLOW_ACCEPT_QUEUE_TIME 50 > /** timeout to slow down log print, so it does not spam the logs, in sec > */ > #define SLOW_LOG_TIME 10 > /** for doq, the maximum dcid length, in ngtcp2 it is 20. */ > @@ -382,6 +384,9 @@ struct comm_point { > * Or leave NULL if it is not used at all. */ > int* tcp_more_write_again; > > + /** resume timer for tcp_more_read_again */ > + struct comm_timer* tcp_more_read_again_timer; > + > /** if set, read/write completes: > read/write state of tcp is toggled. > buffer reset/bytecount reset. > @@ -1130,6 +1135,12 @@ void doq_send_pkt(struct comm_point* c, > > /** doq timer callback function. */ > void doq_timer_cb(void* arg); > + > +/** tcp read again callback function. For tcp req info listen. */ > +void tcp_read_again_cb(void* arg); > + > +/** tcp more read again callback function. For outside network. */ > +void tcp_more_read_again_cb(void* arg); > > /** > * This routine is published for checks and tests, and is only used > internally. > Index: util/proxy_protocol.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/proxy_protocol.c,v > diff -u -p -r1.1.1.3 proxy_protocol.c > --- util/proxy_protocol.c 4 Sep 2024 09:35:36 -0000 1.1.1.3 > +++ util/proxy_protocol.c 20 Sep 2026 09:50:48 -0000 > @@ -185,14 +185,23 @@ pp2_read_header(uint8_t* buf, size_t buf > (header->ver_cmd & 0xF) != PP2_CMD_PROXY) { > return PP_PARSE_UNKNOWN_CMD; > } > - /* Check for supported family and protocol */ > - if(header->fam_prot != PP2_UNSPEC_UNSPEC && > - header->fam_prot != PP2_INET_STREAM && > - header->fam_prot != PP2_INET_DGRAM && > - header->fam_prot != PP2_INET6_STREAM && > - header->fam_prot != PP2_INET6_DGRAM && > - header->fam_prot != PP2_UNIX_STREAM && > - header->fam_prot != PP2_UNIX_DGRAM) { > + /* Check for supported family and protocol, and that len covers > + * the per-family address block (proxy-protocol.txt s2.2). */ > + switch(header->fam_prot) { > + case PP2_UNSPEC_UNSPEC: > + break; > + case PP2_INET_STREAM: > + case PP2_INET_DGRAM: > + if(ntohs(header->len) < PP2_HEADER_LEN_INET) > + return PP_PARSE_SIZE; > + break; > + case PP2_INET6_STREAM: > + case PP2_INET6_DGRAM: > + if(ntohs(header->len) < PP2_HEADER_LEN_INET6) > + return PP_PARSE_SIZE; > + break; > + default: > + /* PP2_UNIX_STREAM, PP2_UNIX_DGRAM, others. */ > return PP_PARSE_UNKNOWN_FAM_PROT; > } > /* We have a correct header */ > Index: util/proxy_protocol.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/proxy_protocol.h,v > diff -u -p -r1.1.1.2 proxy_protocol.h > --- util/proxy_protocol.h 12 Apr 2024 15:44:28 -0000 1.1.1.2 > +++ util/proxy_protocol.h 20 Sep 2026 09:50:48 -0000 > @@ -54,6 +54,15 @@ > /** PROXYv2 version (protocol value) */ > #define PP2_VERSION 0x2 > > +/** PROXYv2 minimum header.len value for TCP/UDP over IPv4 */ > +#define PP2_HEADER_LEN_INET 12 > + > +/** PROXYv2 minimum header.len value for TCP/UDP over IPv6 */ > +#define PP2_HEADER_LEN_INET6 36 > + > +/** PROXYv2 minimum header.len value for TCP/UDP over AF_UNIX */ > +#define PP2_HEADER_LEN_UNIX 216 > + > /** > * PROXYv2 command (protocol value). > */ > Index: util/tube.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/tube.c,v > diff -u -p -r1.10 tube.c > --- util/tube.c 31 Aug 2025 21:41:10 -0000 1.10 > +++ util/tube.c 20 Sep 2026 09:50:48 -0000 > @@ -145,6 +145,20 @@ void tube_remove_bg_write(struct tube* t > } > } > > +/** Drain the pipe of bytes. */ > +static void > +fd_drain(int fd, uint32_t len) > +{ > + uint8_t discard[256]; > + uint32_t remaining = len; > + while(remaining > 0) { > + ssize_t n = read(fd, discard, > + remaining < sizeof(discard) ? remaining : > sizeof(discard)); > + if(n <= 0) break; > + remaining -= (uint32_t)n; > + } > +} > + > int > tube_handle_listen(struct comm_point* c, void* arg, int error, > struct comm_reply* ATTR_UNUSED(reply_info)) > @@ -184,6 +198,9 @@ tube_handle_listen(struct comm_point* c, > tube->cmd_msg = (uint8_t*)calloc(1, tube->cmd_len); > if(!tube->cmd_msg) { > log_err("malloc failure"); > + /* Drain the remaining bytes, since they belong to > this > + * message. The next message starts after it. */ > + fd_drain(c->fd, tube->cmd_len); > tube->cmd_read = 0; > return 0; > } > @@ -374,6 +391,9 @@ int tube_read_msg(struct tube* tube, uin > *buf = (uint8_t*)malloc(*len); > if(!*buf) { > log_err("tube read out of memory"); > + /* Drain the remaining bytes, since they belong to this > + * message. The next message starts after it. */ > + fd_drain(fd, *len); > (void)fd_set_nonblock(fd); > return 0; > } > Index: util/data/dname.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/data/dname.c,v > diff -u -p -r1.9 dname.c > --- util/data/dname.c 26 Sep 2025 07:32:37 -0000 1.9 > +++ util/data/dname.c 20 Sep 2026 09:50:48 -0000 > @@ -192,34 +192,34 @@ pkt_dname_len(sldns_buffer* pkt) > while(1) { > /* read next label */ > if(sldns_buffer_remaining(pkt) < 1) > - return 0; > + goto fail; > labellen = sldns_buffer_read_u8(pkt); > if(LABEL_IS_PTR(labellen)) { > /* compression ptr */ > uint16_t ptr; > if(sldns_buffer_remaining(pkt) < 1) > - return 0; > + goto fail; > ptr = PTR_OFFSET(labellen, > sldns_buffer_read_u8(pkt)); > if(ptrcount++ > MAX_COMPRESS_PTRS) > - return 0; /* loop! */ > + goto fail; /* loop! */ > if(sldns_buffer_limit(pkt) <= ptr) > - return 0; /* out of bounds! */ > + goto fail; /* out of bounds! */ > if(!endpos) > endpos = sldns_buffer_position(pkt); > sldns_buffer_set_position(pkt, ptr); > } else { > /* label contents */ > if(labellen > 0x3f) > - return 0; /* label too long */ > + goto fail; /* label too long */ > len += 1 + labellen; > if(len > LDNS_MAX_DOMAINLEN) > - return 0; > + goto fail; > if(labellen == 0) { > /* end of dname */ > break; > } > if(sldns_buffer_remaining(pkt) < labellen) > - return 0; > + goto fail; > sldns_buffer_skip(pkt, (ssize_t)labellen); > } > } > @@ -227,6 +227,13 @@ pkt_dname_len(sldns_buffer* pkt) > sldns_buffer_set_position(pkt, endpos); > > return len; > +fail: > + /* Restore the position on failure too: callers (rdata_copy) > compute > + * the consumed field length from the buffer position and must not > + * see a partial walk of a name that failed to parse. */ > + if(endpos) > + sldns_buffer_set_position(pkt, endpos); > + return 0; > } > > int > Index: util/data/msgencode.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/data/msgencode.c,v > diff -u -p -r1.18 msgencode.c > --- util/data/msgencode.c 26 May 2026 11:14:11 -0000 1.18 > +++ util/data/msgencode.c 20 Sep 2026 09:50:48 -0000 > @@ -634,7 +634,7 @@ insert_query(struct query_info* qinfo, s > size_t qname_len = qinfo->local_alias ? > qinfo->local_alias->rrset->rk.dname_len : qinfo->qname_len; > if(sldns_buffer_remaining(buffer) < > - qinfo->qname_len+sizeof(uint16_t)*2) > + qname_len+sizeof(uint16_t)*2) > return RETVAL_TRUNC; /* buffer too small */ > /* the query is the first name inserted into the tree */ > if(!compress_tree_store(qname, dname_count_labels(qname), > @@ -1129,9 +1129,11 @@ extended_error_encode(sldns_buffer* buf, > sldns_buffer_write(buf, &flags, sizeof(uint16_t)); > sldns_buffer_write(buf, &flags, sizeof(uint16_t)); > if(qinfo) { > - const uint8_t* qname = qinfo->local_alias ? > + const uint8_t* qname = > + (qinfo->local_alias && qinfo->local_alias->rrset) ? > qinfo->local_alias->rrset->rk.dname : qinfo->qname; > - size_t qname_len = qinfo->local_alias ? > + size_t qname_len = > + (qinfo->local_alias && qinfo->local_alias->rrset) ? > qinfo->local_alias->rrset->rk.dname_len : > qinfo->qname_len; > if(sldns_buffer_current(buf) == qname) > Index: util/data/msgparse.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/data/msgparse.c,v > diff -u -p -r1.15 msgparse.c > --- util/data/msgparse.c 27 Jul 2026 14:14:39 -0000 1.15 > +++ util/data/msgparse.c 20 Sep 2026 09:50:48 -0000 > @@ -1033,8 +1033,11 @@ parse_edns_options_from_query(uint8_t* r > 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, > @@ -1089,10 +1092,10 @@ parse_edns_options_from_query(uint8_t* r > cookie_is_v4, server_cookie, now); > } else { > /* Use the cookie option value to > validate. */ > - cookie_val_status = edns_cookie_server_validate( > - rdata_ptr, opt_len, cfg->cookie_secret, > - cfg->cookie_secret_len, cookie_is_v4, > - server_cookie, now); > + cookie_val_status = > edns_cookie_server_validate( > + rdata_ptr, opt_len, > cfg->cookie_secret, > + cfg->cookie_secret_len, > cookie_is_v4, > + server_cookie, now); > } > if(cookie_val_status == COOKIE_STATUS_VALID_RENEW) > edns->cookie_valid = 1; > @@ -1133,8 +1136,8 @@ parse_edns_options_from_query(uint8_t* r > cookie_is_v4, now); > > lock_basic_unlock(&cookie_secrets->lock); > } else { > - edns_cookie_server_write(server_cookie, > - cfg->cookie_secret, cookie_is_v4, > now); > + > edns_cookie_server_write(server_cookie, > + cfg->cookie_secret, > cookie_is_v4, now); > } > > if(!edns_opt_list_append(&edns->opt_list_out, > LDNS_EDNS_COOKIE, 24, > server_cookie, > Index: util/data/msgreply.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/data/msgreply.c,v > diff -u -p -r1.29 msgreply.c > --- util/data/msgreply.c 26 May 2026 11:14:11 -0000 1.29 > +++ util/data/msgreply.c 20 Sep 2026 09:50:48 -0000 > @@ -248,6 +248,7 @@ rdata_copy(sldns_buffer* pkt, struct pac > sldns_pkt_section section) > { > uint16_t pkt_len; > + size_t tolen; > uint32_t ttl; > const sldns_rr_descriptor* desc; > > @@ -293,9 +294,13 @@ rdata_copy(sldns_buffer* pkt, struct pac > (rr->ttl_data - sldns_buffer_begin(pkt) + > sizeof(uint32_t))); > /* insert decompressed size into rdata len stored in memory */ > /* -2 because rdatalen bytes are not included. */ > + tolen = rr->size; > + if(tolen < 2) > + return 0; > pkt_len = htons(rr->size - 2); > memmove(to, &pkt_len, sizeof(uint16_t)); > to += 2; > + tolen -= 2; > /* read packet rdata len */ > pkt_len = sldns_buffer_read_u16(pkt); > if(sldns_buffer_remaining(pkt) < pkt_len) > @@ -304,16 +309,29 @@ rdata_copy(sldns_buffer* pkt, struct pac > if(pkt_len > 0 && desc && desc->_dname_count > 0) { > int count = (int)desc->_dname_count; > int rdf = 0; > - size_t len; > - size_t oldpos; > + size_t len, dlen; > + size_t oldpos, newpos; > /* decompress dnames. */ > while(pkt_len > 0 && count) { > switch(desc->_wireformat[rdf]) { > case LDNS_RDF_TYPE_DNAME: > oldpos = sldns_buffer_position(pkt); > - dname_pkt_copy(pkt, to, > + dlen = pkt_dname_len(pkt); > + if(dlen == 0) > + return 0; /* malformed */ > + if(dlen > tolen) > + return 0; /* alloc mismatch */ > + newpos = sldns_buffer_position(pkt); > + if(oldpos > newpos) > + return 0; /* should have moved > forward*/ > + sldns_buffer_set_position(pkt, oldpos); > + dname_pkt_copy(pkt, to, > sldns_buffer_current(pkt)); > - to += pkt_dname_len(pkt); > + sldns_buffer_set_position(pkt, newpos); > + to += dlen; > + tolen -= dlen; > + if(sldns_buffer_position(pkt)-oldpos > > pkt_len) > + return 0; /* malformed: walks > diverged */ > pkt_len -= > sldns_buffer_position(pkt)-oldpos; > count--; > len = 0; > @@ -326,9 +344,12 @@ rdata_copy(sldns_buffer* pkt, struct pac > break; > } > if(len) { > + if(len > tolen) > + return 0; /* alloc mismatch */ > log_assert(len <= pkt_len); > memmove(to, sldns_buffer_current(pkt), > len); > to += len; > + tolen -= len; > sldns_buffer_skip(pkt, (ssize_t)len); > pkt_len -= len; > } > @@ -336,8 +357,11 @@ rdata_copy(sldns_buffer* pkt, struct pac > } > } > /* copy remaining rdata */ > - if(pkt_len > 0) > + if(pkt_len > 0) { > + if(pkt_len > tolen) > + return 0; /* alloc mismatch */ > memmove(to, sldns_buffer_current(pkt), pkt_len); > + } > > return 1; > } > @@ -483,9 +507,12 @@ parse_copy_decompress_rrset(sldns_buffer > } > pk->entry.data = (void*)data; > pk->entry.key = (void*)pk; > - pk->entry.hash = pset->hash; > - data->trust = get_rrset_trust(msg, pset); > pk->rk.flags |= (data->ttl == 0) ? PACKED_RRSET_UPSTREAM_0TTL : 0; > + if( (pk->rk.flags & PACKED_RRSET_UPSTREAM_0TTL) != 0) > + pk->entry.hash = rrset_key_hash(&pk->rk); > + else > + pk->entry.hash = pset->hash; > + data->trust = get_rrset_trust(msg, pset); > return 1; > } > > @@ -1112,6 +1139,17 @@ reply_all_rrsets_secure(struct reply_inf > return 1; > } > > +int reply_an_ns_rrsets_secure(struct reply_info* rep) > +{ > + size_t i; > + for(i=0; ian_numrrsets+rep->ns_numrrsets; i++) { > + if( ((struct packed_rrset_data*)rep->rrsets[i]->entry.data) > + ->security != sec_status_secure ) > + return 0; > + } > + return 1; > +} > + > struct reply_info* > parse_reply_in_temp_region(sldns_buffer* pkt, struct regional* region, > struct query_info* qi) > @@ -1503,8 +1541,12 @@ struct edns_option* edns_opt_list_find(s > int local_alias_shallow_copy_qname(struct local_rrset* local_alias, > uint8_t** qname, > size_t* qname_len) > { > - struct ub_packed_rrset_key* rrset = local_alias->rrset; > - struct packed_rrset_data* d = rrset->entry.data; > + struct ub_packed_rrset_key* rrset; > + struct packed_rrset_data* d; > + rrset = local_alias->rrset; > + if(!rrset) return 0; > + d = rrset->entry.data; > + if(!d) return 0; > > /* Sanity check: our current implementation only supports > * a single CNAME RRset as a local alias. */ > Index: util/data/msgreply.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/data/msgreply.h,v > diff -u -p -r1.19 msgreply.h > --- util/data/msgreply.h 26 May 2026 11:14:11 -0000 1.19 > +++ util/data/msgreply.h 20 Sep 2026 09:50:48 -0000 > @@ -494,6 +494,9 @@ int reply_check_cname_chain(struct query > */ > int reply_all_rrsets_secure(struct reply_info* rep); > > +/** Check status of answer and authority section RRs. */ > +int reply_an_ns_rrsets_secure(struct reply_info* rep); > + > /** > * Find answer rrset in reply, the one matching qinfo. Follows CNAMEs, so > the > * result may have a different owner name. > Index: util/data/packed_rrset.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/data/packed_rrset.c,v > diff -u -p -r1.9 packed_rrset.c > --- util/data/packed_rrset.c 27 Jul 2026 14:14:39 -0000 1.9 > +++ util/data/packed_rrset.c 20 Sep 2026 09:50:48 -0000 > @@ -297,7 +297,7 @@ int packed_rr_to_string(struct ub_packed > wlen = (size_t)sldns_wire2str_rr_buf(rr, rlen, dest, dest_len); > if(wlen >= dest_len) { > /* the output string was truncated */ > - log_info("rrbuf failure %d %s", (int)d->rr_len[i], dest); > + verbose(VERB_ALGO, "rrbuf failure %d %s", > (int)d->rr_len[i], dest); > dest[0] = 0; > return 0; > } > @@ -363,8 +363,11 @@ packed_rrset_copy_region(struct ub_packe > * of the novel ghost attack mitigation i.e., using the > * qstarttime for NS RRSets. In that case make sure that > the > * returned TTL is not higher than the original one. */ > - log_assert(d->ttl_add <= now || > - (ntohs(key->rk.type) == LDNS_RR_TYPE_NS)); > + /* For types other than type NS, auth zone and rpz code > + * can have ttl_add values. Also time could conceivably > move > + * in reverse, due to operator action, and it is prudent > + * to not assert on that here. > + * So there is no assertion d->ttl_add <= now || type==NS > */ > now_control = SERVE_ORIGINAL_TTL ? data->ttl_add > : (d->ttl_add > now ? d->ttl_add : now ); > for(i=0; icount + d->rrsig_count; i++) { > Index: util/data/packed_rrset.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/data/packed_rrset.h,v > diff -u -p -r1.9 packed_rrset.h > --- util/data/packed_rrset.h 26 May 2026 11:14:11 -0000 1.9 > +++ util/data/packed_rrset.h 20 Sep 2026 09:50:48 -0000 > @@ -72,6 +72,8 @@ typedef uint64_t rrset_id_type; > #define PACKED_RRSET_UNVERIFIED_GLUE 0x10 > /** this rrset has a 0TTL from upstream */ > #define PACKED_RRSET_UPSTREAM_0TTL 0x20 > +/** this rrset has 0TTL from upstream and also has had grace TTL applied > */ > +#define PACKED_RRSET_0TTL_GRACE 0x40 > > /** number of rrs and rrsets for integer overflow protection. More than > * this is not really possible (64K packet has much less RRs and RRsets) > in > Index: util/shm_side/shm_main.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/shm_side/shm_main.c,v > diff -u -p -r1.9 shm_main.c > --- util/shm_side/shm_main.c 26 May 2026 11:14:11 -0000 1.9 > +++ util/shm_side/shm_main.c 20 Sep 2026 09:50:48 -0000 > @@ -351,6 +351,8 @@ void shm_main_run(struct worker *worker) > int offset; > double total_mesh_time_median; > struct shm_main_info* shm_info = worker->daemon->shm_info; > + if(!shm_info) > + return; > > #ifndef S_SPLINT_S > verbose(VERB_DETAIL, "SHM run - worker [%d] - daemon [%p] - > timenow(%u) - timeboot(%u)", > Index: util/storage/lookup3.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/util/storage/lookup3.c,v > diff -u -p -r1.8 lookup3.c > --- util/storage/lookup3.c 4 Sep 2024 09:36:41 -0000 1.8 > +++ util/storage/lookup3.c 20 Sep 2026 09:50:48 -0000 > @@ -255,10 +255,10 @@ uint32_t initval) /* the > { > case 3 : c+=k[2]; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 2 : b+=k[1]; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 1 : a+=k[0]; > final(a,b,c); > ATTR_FALLTHROUGH > @@ -531,37 +531,37 @@ uint32_t hashlittle( const void *key, si > { > case 12: c+=((uint32_t)k[11])<<24; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 11: c+=((uint32_t)k[10])<<16; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 10: c+=((uint32_t)k[9])<<8; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 9 : c+=k[8]; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 8 : b+=((uint32_t)k[7])<<24; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 7 : b+=((uint32_t)k[6])<<16; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 6 : b+=((uint32_t)k[5])<<8; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 5 : b+=k[4]; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 4 : a+=((uint32_t)k[3])<<24; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 3 : a+=((uint32_t)k[2])<<16; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 2 : a+=((uint32_t)k[1])<<8; > ATTR_FALLTHROUGH > - /* fallthrough */ > + /* fallthrough */ > case 1 : a+=k[0]; > break; > case 0 : return c; > Index: validator/autotrust.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/autotrust.c,v > diff -u -p -r1.20 autotrust.c > --- validator/autotrust.c 31 Aug 2025 21:41:10 -0000 1.20 > +++ validator/autotrust.c 20 Sep 2026 09:50:48 -0000 > @@ -160,10 +160,12 @@ verbose_key(struct autr_ta* ta, enum ver > * Parse comments > * @param str: to parse > * @param ta: trust key autotrust metadata > + * @param header_seen: if an autotrust file header was seen. > + * Without such a header it is a list of resource records. > * @return false on failure. > */ > static int > -parse_comments(char* str, struct autr_ta* ta) > +parse_comments(char* str, struct autr_ta* ta, int header_seen) > { > int len = (int)strlen(str), pos = 0, timestamp = 0; > char* comment = (char*) malloc(sizeof(char)*len+1); > @@ -196,10 +198,18 @@ parse_comments(char* str, struct autr_ta > free(comment); > return 0; > } > - if (pos <= 0) > - ta->s = AUTR_STATE_VALID; > - else > - { > + if (pos <= 0) { > + if(header_seen) { > + /* There was an autotrust trust anchor file header, > + * with a ;; id=.. line, so the entries > + * have to have ;;state= annotations. */ > + log_err("trust anchor in state file has no > ;;state= " > + "annotation, ignoring"); > + free(comment); > + return 0; > + } > + ta->s = AUTR_STATE_VALID; > + } else { > int s = (int) comments[pos] - '0'; > switch(s) > { > @@ -391,6 +401,15 @@ autr_rrset_delete(struct ub_packed_rrset > } > } > > +/** delete autotrust key data */ > +static void > +autr_ta_delete(struct autr_ta* ta) > +{ > + if(!ta) return; > + free(ta->rr); > + free(ta); > +} > + > void autr_point_delete(struct trust_anchor* tp) > { > if(!tp) > @@ -404,8 +423,7 @@ void autr_point_delete(struct trust_anch > struct autr_ta* p = tp->autr->keys, *np; > while(p) { > np = p->next; > - free(p->rr); > - free(p); > + autr_ta_delete(p); > p = np; > } > free(tp->autr->file); > @@ -449,8 +467,7 @@ add_trustanchor_frm_rr(struct val_anchor > return NULL; > *tp = find_add_tp(anchors, rr, rr_len, dname_len); > if(!*tp) { > - free(ta->rr); > - free(ta); > + autr_ta_delete(ta); > return NULL; > } > /* add ta to tp */ > @@ -523,12 +540,14 @@ add_trustanchor_frm_str(struct val_ancho > * @param prev: passed to ldns. > * @param prev_len: length of prev > * @param skip: if true, the result is NULL, but not an error, skip it. > + * @param header_seen: if an autotrust file header was seen. > + * Without such a header it is a list of resource records. > * @return false on failure, otherwise the tp read. > */ > static struct trust_anchor* > load_trustanchor(struct val_anchors* anchors, char* str, const char* > fname, > uint8_t* origin, size_t origin_len, uint8_t** prev, size_t* > prev_len, > - int* skip) > + int* skip, int header_seen) > { > struct autr_ta* ta = NULL; > struct trust_anchor* tp = NULL; > @@ -538,7 +557,11 @@ load_trustanchor(struct val_anchors* anc > if(!ta) > return NULL; > lock_basic_lock(&tp->lock); > - if(!parse_comments(str, ta)) { > + if(!parse_comments(str, ta, header_seen)) { > + /* ta was already linked into the list of keys, unlink it > */ > + log_assert(tp->autr->keys == ta); > + tp->autr->keys = ta->next; > + autr_ta_delete(ta); > lock_basic_unlock(&tp->lock); > return NULL; > } > @@ -846,19 +869,32 @@ parse_id(struct val_anchors* anchors, ch > * @param anchors: the anchor is added to this, if "id:" is seen. > * @param anchor: the anchor as result value or previously returned anchor > * value to read the variable lines into. > + * @param header_seen: if a header ';;id: example.com.' was seen. > + * @param nm: file name. > * @return: 0 no match, -1 failed syntax error, +1 success line read. > * +2 revoked trust anchor file. > */ > static int > parse_var_line(char* line, struct val_anchors* anchors, > - struct trust_anchor** anchor) > + struct trust_anchor** anchor, int* header_seen, const char* nm) > { > struct trust_anchor* tp = *anchor; > int r = 0; > if(strncmp(line, ";;id: ", 6) == 0) { > + *header_seen = 1; > *anchor = parse_id(anchors, line+6); > if(!*anchor) return -1; > - else 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) { > log_err("REVOKED statement must be at start of > file"); > @@ -992,14 +1028,15 @@ int autr_read_file(struct val_anchors* a > FILE* fd; > /* keep track of line numbers */ > int line_nr = 0; > - /* single line */ > - char line[10240]; > + /* single line, enough space for large DNSKEY, 64K, in hex and > dname */ > + char line[10240+65536*2]; > /* trust point being read */ > struct trust_anchor *tp = NULL, *tp2; > int r; > /* for $ORIGIN parsing */ > uint8_t *origin=NULL, *prev=NULL; > size_t origin_len=0, prev_len=0; > + int header_seen = 0; > > if (!(fd = fopen(nm, "r"))) { > log_err("unable to open %s for reading: %s", > @@ -1008,7 +1045,7 @@ int autr_read_file(struct val_anchors* a > } > verbose(VERB_ALGO, "reading autotrust anchor file %s", nm); > while ( (r=read_multiline(line, sizeof(line), fd, &line_nr)) != > 0) { > - if(r == -1 || (r = parse_var_line(line, anchors, &tp)) == > -1) { > + if(r == -1 || (r = parse_var_line(line, anchors, &tp, > &header_seen, nm)) == -1) { > log_err("could not parse auto-trust-anchor-file " > "%s line %d", nm, line_nr); > fclose(fd); > @@ -1030,7 +1067,7 @@ int autr_read_file(struct val_anchors* a > continue; > r = 0; > if(!(tp2=load_trustanchor(anchors, line, nm, origin, > - origin_len, &prev, &prev_len, &r))) { > + origin_len, &prev, &prev_len, &r, header_seen))) { > if(!r) log_err("failed to load trust anchor from > %s " > "at line %i, skipping", nm, line_nr); > /* try to do the rest */ > @@ -1194,6 +1231,11 @@ void autr_write_file(struct module_env* > #endif > char tempf[2048]; > log_assert(tp->autr); > + if(!fname) { > + log_err("autotrust: trust point has no backing file, " > + "skipping write"); > + return; > + } > if(!env) { > log_err("autr_write_file: Module environment is NULL."); > return; > @@ -1255,12 +1297,13 @@ void autr_write_file(struct module_env* > * @param tp: trust point to verify with > * @param rrset: DNSKEY rrset to verify. > * @param qstate: qstate with region. > + * @param vq: validator query state. > * @return false on failure, true if verification successful. > */ > static int > verify_dnskey(struct module_env* env, struct val_env* ve, > struct trust_anchor* tp, struct ub_packed_rrset_key* rrset, > - struct module_qstate* qstate) > + struct module_qstate* qstate, struct val_qstate* vq) > { > char reasonbuf[256]; > char* reason = NULL; > @@ -1268,7 +1311,7 @@ verify_dnskey(struct module_env* env, st > int downprot = env->cfg->harden_algo_downgrade; > enum sec_status sec = val_verify_DNSKEY_with_TA(env, ve, rrset, > tp->ds_rrset, tp->dnskey_rrset, downprot?sigalg:NULL, > &reason, > - NULL, qstate, reasonbuf, sizeof(reasonbuf)); > + NULL, qstate, vq, reasonbuf, sizeof(reasonbuf)); > /* sigalg is ignored, it returns algorithms signalled to exist, but > * in 5011 there are no other rrsets to check. if downprot is > * enabled, then it checks that the DNSKEY is signed with all > @@ -1308,16 +1351,18 @@ min_expiry(struct module_env* env, struc > static int > rr_is_selfsigned_revoked(struct module_env* env, struct val_env* ve, > struct ub_packed_rrset_key* dnskey_rrset, size_t i, > - struct module_qstate* qstate) > + struct module_qstate* qstate, struct val_qstate* vq) > { > enum sec_status sec; > char* reason = NULL; > + size_t num_tagmatches = 0; > verbose(VERB_ALGO, "seen REVOKE flag, check self-signed, rr %d", > (int)i); > /* no algorithm downgrade protection necessary, if it is selfsigned > * revoked it can be removed. */ > sec = dnskey_verify_rrset(env, ve, dnskey_rrset, dnskey_rrset, i, > - &reason, NULL, LDNS_SECTION_ANSWER, qstate); > + &reason, NULL, LDNS_SECTION_ANSWER, qstate, vq, > + &num_tagmatches); > return (sec == sec_status_secure); > } > > @@ -1533,7 +1578,7 @@ init_events(struct trust_anchor* tp) > static void > check_contains_revoked(struct module_env* env, struct val_env* ve, > struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset, > - int* changed, struct module_qstate* qstate) > + int* changed, struct module_qstate* qstate, struct val_qstate* vq) > { > struct packed_rrset_data* dd = (struct packed_rrset_data*) > dnskey_rrset->entry.data; > @@ -1553,7 +1598,8 @@ check_contains_revoked(struct module_env > } > if(!ta) > continue; /* key not found */ > - if(rr_is_selfsigned_revoked(env, ve, dnskey_rrset, i, > qstate)) { > + if(rr_is_selfsigned_revoked(env, ve, dnskey_rrset, i, > qstate, > + vq)) { > /* checked if there is an rrsig signed by this > key. */ > /* same keytag, but stored can be revoked already, > so > * compare keytags, with +0 or +128(REVOKE flag) */ > @@ -1992,8 +2038,7 @@ autr_cleanup_keys(struct trust_anchor* t > != LDNS_RR_TYPE_DNSKEY) { > struct autr_ta* np = p->next; > /* remove */ > - free(p->rr); > - free(p); > + autr_ta_delete(p); > /* snip and go to next item */ > *prevp = np; > p = np; > @@ -2168,7 +2213,7 @@ autr_tp_remove(struct module_env* env, s > > int autr_process_prime(struct module_env* env, struct val_env* ve, > struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset, > - struct module_qstate* qstate) > + struct module_qstate* qstate, struct val_qstate* vq) > { > int changed = 0; > log_assert(tp && tp->autr); > @@ -2209,7 +2254,7 @@ int autr_process_prime(struct module_env > return 1; /* trust point exists */ > } > /* check for revoked keys to remove immediately */ > - check_contains_revoked(env, ve, tp, dnskey_rrset, &changed, > qstate); > + check_contains_revoked(env, ve, tp, dnskey_rrset, &changed, > qstate, vq); > if(changed) { > verbose(VERB_ALGO, "autotrust: revokedkeys, reassemble"); > if(!autr_assemble(tp)) { > @@ -2225,7 +2270,7 @@ int autr_process_prime(struct module_env > } > } > /* verify the dnskey rrset and see if it is valid. */ > - if(!verify_dnskey(env, ve, tp, dnskey_rrset, qstate)) { > + if(!verify_dnskey(env, ve, tp, dnskey_rrset, qstate, vq)) { > verbose(VERB_ALGO, "autotrust: dnskey did not verify."); > /* only increase failure count if this is not the first > prime, > * this means there was a previous successful probe */ > @@ -2318,7 +2363,7 @@ autr_debug_print_tp(struct trust_anchor* > if(tp->dnskey_rrset) { > log_packed_rrset(NO_VERBOSE, "DNSKEY:", tp->dnskey_rrset); > } > - log_info("file %s", tp->autr->file); > + log_info("file %s", (tp->autr->file?tp->autr->file:"null")); > (void)autr_ctime_r(&tp->autr->last_queried, buf); > if(buf[0]) buf[strlen(buf)-1]=0; /* remove newline */ > log_info("last_queried: %u %s", (unsigned)tp->autr->last_queried, > buf); > @@ -2416,7 +2461,7 @@ probe_anchor(struct module_env* env, str > qinfo.qclass); > > if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, > - &probe_answer_cb, env, 0)) { > + &probe_answer_cb, env, 0, NULL)) { > log_err("out of memory making 5011 probe"); > } > } > Index: validator/autotrust.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/autotrust.h,v > diff -u -p -r1.4 autotrust.h > --- validator/autotrust.h 20 Sep 2018 23:15:40 -0000 1.4 > +++ validator/autotrust.h 20 Sep 2026 09:50:48 -0000 > @@ -50,6 +50,7 @@ struct module_env; > struct module_qstate; > struct val_env; > struct sldns_buffer; > +struct val_qstate; > > /** Autotrust anchor states */ > typedef enum { > @@ -190,13 +191,14 @@ void autr_point_delete(struct trust_anch > * @param dnskey_rrset: DNSKEY rrset probed (can be NULL if bad prime > result). > * allocated in a region. Has not been validated yet. > * @param qstate: qstate with region. > + * @param vq: validator query state. > * @return false if trust anchor was revoked completely. > * Otherwise logs errors to log, does not change return value. > * On errors, likely the trust point has been unchanged. > */ > int autr_process_prime(struct module_env* env, struct val_env* ve, > struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset, > - struct module_qstate* qstate); > + struct module_qstate* qstate, struct val_qstate* vq); > > /** > * Debug printout of rfc5011 tracked anchors > Index: validator/val_anchor.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/val_anchor.c,v > diff -u -p -r1.11 val_anchor.c > --- validator/val_anchor.c 31 Aug 2025 21:41:10 -0000 1.11 > +++ validator/val_anchor.c 20 Sep 2026 09:50:48 -0000 > @@ -534,7 +534,10 @@ readkeyword_bindfile(FILE* in, sldns_buf > while((c = getc(in)) != EOF ) { > if(comments && c == '#') { /* # blabla */ > skip_to_eol(in, &c); > - if(c == EOF) return 0; > + if(c == EOF) { > + log_err("trusted-keys, %d, got EOF", > *line); > + return 0; > + } > (*line)++; > continue; > } else if(comments && c=='/' && numdone>0 && /* /_/ bla*/ > @@ -543,7 +546,10 @@ readkeyword_bindfile(FILE* in, sldns_buf > sldns_buffer_skip(buf, -1); > numdone--; > skip_to_eol(in, &c); > - if(c == EOF) return 0; > + if(c == EOF) { > + log_err("trusted-keys, %d, got EOF", > *line); > + return 0; > + } > (*line)++; > continue; > } else if(comments && c=='*' && numdone>0 && /* /_* bla > *_/ */ > @@ -560,7 +566,10 @@ readkeyword_bindfile(FILE* in, sldns_buf > if(c == '\n') > (*line)++; > } > - if(c == EOF) return 0; > + if(c == EOF) { > + log_err("trusted-keys, %d, got EOF", > *line); > + return 0; > + } > continue; > } > /* not a comment, complete the keyword */ > @@ -581,7 +590,8 @@ readkeyword_bindfile(FILE* in, sldns_buf > } > /* space for 1 char + 0 string terminator */ > if(sldns_buffer_remaining(buf) < 2) { > - fatal_exit("trusted-keys, %d, string too long", > *line); > + log_err("trusted-keys, %d, string too long", > *line); > + return 0; > } > sldns_buffer_write_u8(buf, (uint8_t)c); > numdone++; > @@ -595,7 +605,10 @@ readkeyword_bindfile(FILE* in, sldns_buf > break; > } > } > - if(c == EOF) return 0; > + if(c == EOF) { > + log_err("trusted-keys, %d, got EOF", > *line); > + return 0; > + } > return numdone; > } > if(is_bind_special(c)) > @@ -623,7 +636,7 @@ skip_to_special(FILE* in, sldns_buffer* > } > return 1; > } > - log_err("trusted-keys, line %d, expected %c got EOF", *line, spec); > + log_err("trusted-keys, line %d, expected %c, read failed", *line, > spec); > return 0; > } > > Index: validator/val_neg.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/val_neg.c,v > diff -u -p -r1.12 val_neg.c > --- validator/val_neg.c 26 May 2026 11:14:11 -0000 1.12 > +++ validator/val_neg.c 20 Sep 2026 09:50:48 -0000 > @@ -938,6 +938,10 @@ void val_neg_addreply(struct val_neg_cac > 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_ > 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_cach > * @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_ > 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) { > @@ -1223,8 +1238,8 @@ neg_params_ok(struct val_neg_zone* zone, > return 0; > return (h == zone->nsec3_hash && it == zone->nsec3_iter && > slen == zone->nsec3_saltlen && > - (slen != 0 && zone->nsec3_salt && s > - && memcmp(zone->nsec3_salt, s, slen) == 0)); > + (slen == 0 || (slen != 0 && zone->nsec3_salt && s > + && memcmp(zone->nsec3_salt, s, slen) == 0))); > } > > /** get next closer for nsec3 proof */ > @@ -1313,7 +1328,7 @@ neg_nsec3_proof_ds(struct val_neg_zone* > !nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_NS)) > return NULL; > if(!(msg = dns_msg_create(qname, qname_len, > - LDNS_RR_TYPE_DS, zone->dclass, region, 1))) > + LDNS_RR_TYPE_DS, zone->dclass, region, 2))) /* ce > + soa */ > return NULL; > /* The cache response means recursion is available. */ > msg->rep->flags |= BIT_RA; > @@ -1430,7 +1445,7 @@ val_neg_getmsg(struct val_neg_cache* neg > > /* 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 > * 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; > Index: validator/val_nsec.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/val_nsec.c,v > diff -u -p -r1.12 val_nsec.c > --- validator/val_nsec.c 4 Sep 2024 09:36:41 -0000 1.12 > +++ validator/val_nsec.c 20 Sep 2026 09:50:48 -0000 > @@ -177,7 +177,8 @@ static int > nsec_verify_rrset(struct module_env* env, struct val_env* ve, > struct ub_packed_rrset_key* nsec, struct key_entry_key* kkey, > char** reason, sldns_ede_code* reason_bogus, > - struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) > + struct module_qstate* qstate, struct val_qstate* vq, char* > reasonbuf, > + size_t reasonlen) > { > struct packed_rrset_data* d = (struct packed_rrset_data*) > nsec->entry.data; > @@ -189,7 +190,7 @@ nsec_verify_rrset(struct module_env* env > if(d->security == sec_status_secure) > return 1; > d->security = val_verify_rrset_entry(env, ve, nsec, kkey, reason, > - reason_bogus, LDNS_SECTION_AUTHORITY, qstate, &verified, > + reason_bogus, LDNS_SECTION_AUTHORITY, qstate, vq, > &verified, > reasonbuf, reasonlen); > if(d->security == sec_status_secure) { > rrset_update_sec_status(env->rrset_cache, nsec, *env->now); > @@ -203,7 +204,7 @@ val_nsec_prove_nodata_dsreply(struct mod > struct query_info* qinfo, struct reply_info* rep, > struct key_entry_key* kkey, time_t* proof_ttl, char** reason, > sldns_ede_code* reason_bogus, struct module_qstate* qstate, > - char* reasonbuf, size_t reasonlen) > + struct val_qstate* vq, char* reasonbuf, size_t reasonlen) > { > struct ub_packed_rrset_key* nsec = reply_find_rrset_section_ns( > rep, qinfo->qname, qinfo->qname_len, LDNS_RR_TYPE_NSEC, > @@ -221,26 +222,32 @@ val_nsec_prove_nodata_dsreply(struct mod > * 2) this is not a delegation point */ > if(nsec) { > if(!nsec_verify_rrset(env, ve, nsec, kkey, reason, > - reason_bogus, qstate, reasonbuf, reasonlen)) { > + reason_bogus, qstate, vq, reasonbuf, reasonlen)) { > verbose(VERB_ALGO, "NSEC RRset for the " > "referral did not verify."); > return sec_status_bogus; > } > - sec = val_nsec_proves_no_ds(nsec, qinfo); > - if(sec == sec_status_bogus) { > - /* something was wrong. */ > - *reason = "NSEC does not prove absence of DS"; > - *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > - return sec; > - } else if(sec == sec_status_insecure) { > - /* this wasn't a delegation point. */ > - return sec; > - } else if(sec == sec_status_secure) { > - /* this proved no DS. */ > - *proof_ttl = ub_packed_rrset_ttl(nsec); > - return sec; > + /* If the NSEC was a wildcard, the verify rewrites the > + * owner to '*.zone'. Check the NSEC owner matches. */ > + if(query_dname_compare(nsec->rk.dname, qinfo->qname) == 0) > { > + sec = val_nsec_proves_no_ds(nsec, qinfo); > + if(sec == sec_status_bogus) { > + /* something was wrong. */ > + *reason = "NSEC does not prove absence of > DS"; > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + return sec; > + } else if(sec == sec_status_insecure) { > + /* this wasn't a delegation point. */ > + return sec; > + } else if(sec == sec_status_secure) { > + /* this proved no DS. */ > + *proof_ttl = ub_packed_rrset_ttl(nsec); > + return sec; > + } > } > /* if unchecked, fall through to next proof */ > + /* For *.closest-encloser NSEC, there is a closer-match > + * check for the wildcard below. */ > } > > /* Otherwise, there is no NSEC at qname. This could be an ENT. > @@ -252,7 +259,7 @@ val_nsec_prove_nodata_dsreply(struct mod > if(rep->rrsets[i]->rk.type != htons(LDNS_RR_TYPE_NSEC)) > continue; > if(!nsec_verify_rrset(env, ve, rep->rrsets[i], kkey, > reason, > - reason_bogus, qstate, reasonbuf, reasonlen)) { > + reason_bogus, qstate, vq, reasonbuf, reasonlen)) { > verbose(VERB_ALGO, "NSEC for empty non-terminal " > "did not verify."); > *reason = "NSEC for empty non-terminal " > Index: validator/val_nsec.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/val_nsec.h,v > diff -u -p -r1.6 val_nsec.h > --- validator/val_nsec.h 4 Sep 2024 09:36:41 -0000 1.6 > +++ validator/val_nsec.h 20 Sep 2026 09:50:48 -0000 > @@ -52,6 +52,7 @@ struct ub_packed_rrset_key; > struct reply_info; > struct query_info; > struct key_entry_key; > +struct val_qstate; > > /** > * Check DS absence. > @@ -68,6 +69,7 @@ struct key_entry_key; > * @param reason: string explaining why bogus. > * @param reason_bogus: relevant EDE code for validation failure. > * @param qstate: qstate with region. > + * @param vq: validator qstate. > * @param reasonbuf: buffer to use for fail reason string print. > * @param reasonlen: length of reasonbuf. > * @return security status. > @@ -80,7 +82,8 @@ enum sec_status val_nsec_prove_nodata_ds > struct val_env* ve, struct query_info* qinfo, > struct reply_info* rep, struct key_entry_key* kkey, > time_t* proof_ttl, char** reason, sldns_ede_code* reason_bogus, > - struct module_qstate* qstate, char* reasonbuf, size_t reasonlen); > + struct module_qstate* qstate, struct val_qstate* vq, char* > reasonbuf, > + size_t reasonlen); > > /** > * nsec typemap check, takes an NSEC-type bitmap as argument, checks for > type. > Index: validator/val_nsec3.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/val_nsec3.c,v > diff -u -p -r1.11 val_nsec3.c > --- validator/val_nsec3.c 26 May 2026 11:14:11 -0000 1.11 > +++ validator/val_nsec3.c 20 Sep 2026 09:50:48 -0000 > @@ -1248,6 +1248,10 @@ nsec3_prove_nameerror(struct module_env* > 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* en > 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* > 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)) > @@ -1509,7 +1521,8 @@ static int > list_is_secure(struct module_env* env, struct val_env* ve, > struct ub_packed_rrset_key** list, size_t num, > struct key_entry_key* kkey, char** reason, sldns_ede_code > *reason_bogus, > - struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) > + struct module_qstate* qstate, struct val_qstate* vq, char* > reasonbuf, > + size_t reasonlen) > { > struct packed_rrset_data* d; > size_t i; > @@ -1525,7 +1538,7 @@ list_is_secure(struct module_env* env, s > continue; > d->security = val_verify_rrset_entry(env, ve, list[i], > kkey, > reason, reason_bogus, LDNS_SECTION_AUTHORITY, > qstate, > - &verified, reasonbuf, reasonlen); > + vq, &verified, reasonbuf, reasonlen); > if(d->security != sec_status_secure) { > verbose(VERB_ALGO, "NSEC3 did not verify"); > return 0; > @@ -1540,7 +1553,8 @@ nsec3_prove_nods(struct module_env* env, > struct ub_packed_rrset_key** list, size_t num, > struct query_info* qinfo, struct key_entry_key* kkey, char** > reason, > sldns_ede_code* reason_bogus, struct module_qstate* qstate, > - struct nsec3_cache_table* ct, char* reasonbuf, size_t reasonlen) > + struct val_qstate* vq, struct nsec3_cache_table* ct, char* > reasonbuf, > + size_t reasonlen) > { > struct nsec3_filter flt; > struct ce_response ce; > @@ -1556,7 +1570,7 @@ nsec3_prove_nods(struct module_env* env, > return sec_status_bogus; /* no valid NSEC3s, bogus */ > } > if(!list_is_secure(env, ve, list, num, kkey, reason, reason_bogus, > - qstate, reasonbuf, reasonlen)) { > + qstate, vq, reasonbuf, reasonlen)) { > *reason = "not all NSEC3 records secure"; > return sec_status_bogus; /* not all NSEC3 records secure */ > } > @@ -1565,6 +1579,11 @@ nsec3_prove_nods(struct module_env* env, > *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 +1679,10 @@ nsec3_prove_nxornodata(struct module_env > 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)) > Index: validator/val_nsec3.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/val_nsec3.h,v > diff -u -p -r1.8 val_nsec3.h > --- validator/val_nsec3.h 26 May 2026 11:14:11 -0000 1.8 > +++ validator/val_nsec3.h 20 Sep 2026 09:50:48 -0000 > @@ -78,6 +78,7 @@ struct reply_info; > struct query_info; > struct key_entry_key; > struct sldns_buffer; > +struct val_qstate; > > /** > * 0 1 2 3 4 5 6 7 > @@ -215,6 +216,7 @@ nsec3_prove_wildcard(struct module_env* > * @param reason: string for bogus result. > * @param reason_bogus: EDE (RFC8914) code paired with the reason of > failure. > * @param qstate: qstate with region. > + * @param vq: validator qstate. > * @param ct: cached hashes table. > * @param reasonbuf: buffer to use for fail reason string print. > * @param reasonlen: length of reasonbuf. > @@ -230,7 +232,8 @@ nsec3_prove_nods(struct module_env* env, > struct ub_packed_rrset_key** list, size_t num, > struct query_info* qinfo, struct key_entry_key* kkey, char** > reason, > sldns_ede_code* reason_bogus, struct module_qstate* qstate, > - struct nsec3_cache_table* ct, char* reasonbuf, size_t reasonlen); > + struct val_qstate* vq, struct nsec3_cache_table* ct, char* > reasonbuf, > + size_t reasonlen); > > /** > * Prove NXDOMAIN or NODATA. > Index: validator/val_secalgo.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/val_secalgo.c,v > diff -u -p -r1.17 val_secalgo.c > --- validator/val_secalgo.c 4 Sep 2024 09:36:41 -0000 1.17 > +++ validator/val_secalgo.c 20 Sep 2026 09:50:48 -0000 > @@ -745,11 +745,9 @@ verify_canonrrset(sldns_buffer* buf, int > if((algo == LDNS_DSA || algo == LDNS_DSA_NSEC3) > &&(fake_dsa||fake_sha1)) > return sec_status_secure; > #endif > -#ifndef USE_SHA1 > if(fake_sha1 && (algo == LDNS_DSA || algo == LDNS_DSA_NSEC3 || > algo == LDNS_RSASHA1 || algo == LDNS_RSASHA1_NSEC3)) > return sec_status_secure; > -#endif > - > + > if(!setup_key_digest(algo, &evp_key, &digest_type, key, keylen)) { > verbose(VERB_QUERY, "verify: failed to setup key"); > *reason = "use of key for crypto failed"; > @@ -1874,9 +1872,9 @@ _verify_nettle_rsa(sldns_buffer* buf, un > } > mod_offset = exp_offset + exp_len; > nettle_rsa_public_key_init(&pubkey); > - pubkey.size = keylen - mod_offset; > nettle_mpz_set_str_256_u(pubkey.e, exp_len, &key[exp_offset]); > - nettle_mpz_set_str_256_u(pubkey.n, pubkey.size, &key[mod_offset]); > + nettle_mpz_set_str_256_u(pubkey.n, keylen - mod_offset, > &key[mod_offset]); > + pubkey.size = nettle_mpz_sizeinbase_256_u(pubkey.n); > > /* Digest content of "buf" and verify its RSA signature in > "sigblock"*/ > nettle_mpz_init_set_str_256_u(signature, sigblock_len, > (uint8_t*)sigblock); > Index: validator/val_sigcrypt.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/val_sigcrypt.c,v > diff -u -p -r1.18 val_sigcrypt.c > --- validator/val_sigcrypt.c 27 Jul 2026 14:14:39 -0000 1.18 > +++ validator/val_sigcrypt.c 20 Sep 2026 09:50:48 -0000 > @@ -82,6 +82,8 @@ > > /** Maximum number of RRSIG validations for an RRset. */ > #define MAX_VALIDATE_RRSIGS 8 > +/** Maximum number of NSEC validations for a message. */ > +#define MAX_VALIDATE_NSECS 8 > > /** return number of rrs in an rrset */ > static size_t > @@ -305,6 +307,8 @@ ds_create_dnskey_digest(struct module_en > * digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); > * DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. > */ > sldns_buffer_clear(b); > + if(!sldns_buffer_available(b, dnskey_rrset->rk.dname_len + > dnskey_len-2)) > + return 0; /* buffer too small */ > sldns_buffer_write(b, dnskey_rrset->rk.dname, > dnskey_rrset->rk.dname_len); > query_dname_tolower(sldns_buffer_begin(b)); > @@ -546,8 +550,10 @@ int algo_needs_missing(struct algo_needs > * @param reason_bogus: EDE (RFC8914) code paired with the reason of > failure. > * @param section: section of packet where this rrset comes from. > * @param qstate: qstate with region. > + * @param vq: validator qstate with attempt counts. > * @param numverified: incremented when the number of RRSIG validations > * increases. > + * @param num_tagmatches: incremented for tag matches. > * @return secure if any key signs *this* signature. bogus if no key > signs it, > * unchecked on error, or indeterminate if all keys are not supported > by > * the crypto library (openssl3+ only). > @@ -559,7 +565,7 @@ dnskeyset_verify_rrset_sig(struct module > struct rbtree_type** sortree, > char** reason, sldns_ede_code *reason_bogus, > sldns_pkt_section section, struct module_qstate* qstate, > - int* numverified) > + struct val_qstate* vq, int* numverified, size_t* num_tagmatches) > { > /* find matching keys and check them */ > enum sec_status sec = sec_status_bogus; > @@ -578,6 +584,14 @@ dnskeyset_verify_rrset_sig(struct module > } > > for(i=0; i + if((*num_tagmatches)++ > MAX_TAG_MATCHES) { > + *reason = "too many tag matches"; > + if(reason_bogus) > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + verbose(VERB_ALGO, "verify sig: too many tag > matches, " > + "MAX_TAG_MATCHES (%d); bogus", > MAX_TAG_MATCHES); > + return sec_status_bogus; > + } > /* see if key matches keytag and algo */ > if(algo != dnskey_get_algo(dnskey, i) || > tag != dnskey_calc_keytag(dnskey, i)) > @@ -585,6 +599,26 @@ dnskeyset_verify_rrset_sig(struct module > numchecked ++; > (*numverified)++; > > + if(vq && vq->num_validation_attempts++ > > env->cfg->val_validation_attempts) { > + *reason = "too many validation attempts"; > + if(reason_bogus) > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + verbose(VERB_ALGO, "verify sig: too many > validation attempts, " > + "val-validation-attempts (%d); bogus", > env->cfg->val_validation_attempts); > + return sec_status_bogus; > + } > + if(vq && (ntohs(rrset->rk.type) == LDNS_RR_TYPE_NSEC || > + ntohs(rrset->rk.type) == LDNS_RR_TYPE_NSEC3) && > + vq->num_nsec_attempts++ > MAX_VALIDATE_NSECS) { > + *reason = "too many NSEC or NSEC3 validation > attempts"; > + if(reason_bogus) > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + verbose(VERB_ALGO, "verify sig: too many NSEC or > NSEC3 validation attempts, " > + "(%d); bogus", MAX_VALIDATE_NSECS); > + vq->num_nsec_attempts_exceeded = 1; > + return sec_status_bogus; > + } > + > /* see if key verifies */ > sec = dnskey_verify_rrset_sig(env->scratch, > env->scratch_buffer, ve, now, rrset, dnskey, i, > @@ -624,11 +658,12 @@ enum sec_status > dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve, > struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* > dnskey, > uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, > - sldns_pkt_section section, struct module_qstate* qstate, int* > verified, > - char* reasonbuf, size_t reasonlen) > + sldns_pkt_section section, struct module_qstate* qstate, > + struct val_qstate* vq, int* verified, char* reasonbuf, > + size_t reasonlen) > { > enum sec_status sec; > - size_t i, num; > + size_t i, num, num_tagmatches = 0; > rbtree_type* sortree = NULL; > /* make sure that for all DNSKEY algorithms there are valid sigs */ > struct algo_needs needs; > @@ -656,9 +691,19 @@ dnskeyset_verify_rrset(struct module_env > } > } > for(i=0; i + if(num_tagmatches > MAX_TAG_MATCHES) { > + *reason = "too many tag matches"; > + if(reason_bogus) > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + verbose(VERB_ALGO, "rrset failed to verify: too > many tag matches, " > + "MAX_TAG_MATCHES (%d)", MAX_TAG_MATCHES); > + if(reason_bogus) > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + return sec_status_bogus; > + } > sec = dnskeyset_verify_rrset_sig(env, ve, *env->now, > rrset, > dnskey, i, &sortree, reason, reason_bogus, > - section, qstate, verified); > + section, qstate, vq, verified, &num_tagmatches); > /* see which algorithm has been fixed up */ > if(sec == sec_status_secure) { > if(!sigalg) > @@ -707,7 +752,8 @@ enum sec_status > dnskey_verify_rrset(struct module_env* env, struct val_env* ve, > struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* > dnskey, > size_t dnskey_idx, char** reason, sldns_ede_code *reason_bogus, > - sldns_pkt_section section, struct module_qstate* qstate) > + sldns_pkt_section section, struct module_qstate* qstate, > + struct val_qstate* vq, size_t* num_tagmatches) > { > enum sec_status sec; > size_t i, num, numchecked = 0, numindeterminate = 0; > @@ -728,9 +774,26 @@ dnskey_verify_rrset(struct module_env* e > } > for(i=0; i /* see if sig matches keytag and algo */ > + if((*num_tagmatches)++ > MAX_TAG_MATCHES) { > + *reason = "too many tag matches"; > + if(reason_bogus) > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + verbose(VERB_ALGO, "rrset failed to verify: too > many tag matches, " > + "MAX_TAG_MATCHES (%d); bogus", > MAX_TAG_MATCHES); > + return sec_status_bogus; > + } > if(algo != rrset_get_sig_algo(rrset, i) || > tag != rrset_get_sig_keytag(rrset, i)) > continue; > + if(vq && vq->num_validation_attempts++ > > env->cfg->val_validation_attempts) { > + *reason = "too many validation attempts"; > + if(reason_bogus) > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + verbose(VERB_ALGO, "rrset failed to verify: too > many validation attempts, " > + "val-validation-attempts (%d); bogus", > env->cfg->val_validation_attempts); > + return sec_status_bogus; > + } > + > buf_canon = 0; > sec = dnskey_verify_rrset_sig(env->scratch, > env->scratch_buffer, ve, *env->now, rrset, > @@ -1083,6 +1146,18 @@ insert_can_owner(sldns_buffer* buf, stru > } > } > > +/** lowercase a wire dname but never step past end */ > +static void > +canon_dname_tolower(uint8_t* d, uint8_t* end) > +{ > + uint8_t lab; > + while(d < end && (lab = *d) != 0) { > + if((size_t)lab+1 > (size_t)(end-d)) return; /* malformed */ > + for(d++; lab; lab--, d++) > + *d = (uint8_t)tolower((unsigned char)*d); > + } > +} > + > /** > * Canonicalize Rdata in buffer. > * @param buf: buffer at position just after the rdata. > @@ -1094,6 +1169,7 @@ canonicalize_rdata(sldns_buffer* buf, st > size_t len) > { > uint8_t* datstart = sldns_buffer_current(buf)-len+2; > + uint8_t* datend = sldns_buffer_current(buf); > size_t firstlen; > switch(ntohs(rrset->rk.type)) { > case LDNS_RR_TYPE_NXT: > @@ -1107,16 +1183,16 @@ canonicalize_rdata(sldns_buffer* buf, st > case LDNS_RR_TYPE_PTR: > case LDNS_RR_TYPE_DNAME: > /* type only has a single argument, the name */ > - query_dname_tolower(datstart); > + canon_dname_tolower(datstart, datend); > return; > case LDNS_RR_TYPE_MINFO: > case LDNS_RR_TYPE_RP: > case LDNS_RR_TYPE_SOA: > /* two names after another */ > - query_dname_tolower(datstart); > + canon_dname_tolower(datstart, datend); > firstlen = dname_valid(datstart, len-2); > if(firstlen && firstlen < len-2) > - query_dname_tolower(datstart + firstlen); > + canon_dname_tolower(datstart + firstlen, > datend); > return; > case LDNS_RR_TYPE_RT: > case LDNS_RR_TYPE_AFSDB: > @@ -1126,7 +1202,7 @@ canonicalize_rdata(sldns_buffer* buf, st > if(len < 2+2+1) /* rdlen, skiplen, 1byteroot */ > return; > datstart += 2; > - query_dname_tolower(datstart); > + canon_dname_tolower(datstart, datend); > return; > case LDNS_RR_TYPE_SIG: > /* downcase the RRSIG, compat with BIND (kept it from SIG) > */ > @@ -1135,17 +1211,17 @@ canonicalize_rdata(sldns_buffer* buf, st > if(len < 2+18+1) > return; > datstart += 18; > - query_dname_tolower(datstart); > + canon_dname_tolower(datstart, datend); > return; > case LDNS_RR_TYPE_PX: > /* skip, then two names after another */ > if(len < 2+2+1) > return; > datstart += 2; > - query_dname_tolower(datstart); > + canon_dname_tolower(datstart, datend); > firstlen = dname_valid(datstart, len-2-2); > if(firstlen && firstlen < len-2-2) > - query_dname_tolower(datstart + firstlen); > + canon_dname_tolower(datstart + firstlen, > datend); > return; > case LDNS_RR_TYPE_NAPTR: > if(len < 2+4) > @@ -1166,14 +1242,14 @@ canonicalize_rdata(sldns_buffer* buf, st > datstart += (size_t)datstart[0]+1; > if(len < 1) /* check name is at least 1 byte*/ > return; > - query_dname_tolower(datstart); > + canon_dname_tolower(datstart, datend); > return; > case LDNS_RR_TYPE_SRV: > /* skip fixed part */ > if(len < 2+6+1) > return; > datstart += 6; > - query_dname_tolower(datstart); > + canon_dname_tolower(datstart, datend); > return; > > /* do not canonicalize NSEC rdata name, compat with > @@ -1295,14 +1371,32 @@ rrset_canonical(struct regional* region, > } > > sldns_buffer_clear(buf); > + if(sldns_buffer_remaining(buf) < siglen || siglen < 18+1) { > + verbose(VERB_ALGO, "verify: failed to canonicalize, " > + "rrset too big"); > + return 0; > + } > sldns_buffer_write(buf, sig, siglen); > /* canonicalize signer name */ > - query_dname_tolower(sldns_buffer_begin(buf)+18); > + canon_dname_tolower(sldns_buffer_begin(buf)+18, > + sldns_buffer_current(buf)); > + > + if(sldns_buffer_remaining(buf) < k->rk.dname_len+2) { > + /* Check if the first can_owner name can fit in the buffer. > + * The length is k->rk.dname_len or k->rk.dname_len+2 > + * if it has '*.' in prefixed. Checks the upper bound, > + * also realistically the rest of the rrtype, rrclass, > origttl, > + * rdata and so on has to be inserted, so that extra space > has > + * to be there. */ > + verbose(VERB_ALGO, "verify: failed to canonicalize, " > + "rrset too big"); > + return 0; > + } > RBTREE_FOR(walk, struct canon_rr*, (*sortree)) { > /* see if there is enough space left in the buffer */ > if(sldns_buffer_remaining(buf) < can_owner_len + 2 + 2 + 4 > + d->rr_len[walk->rr_idx]) { > - log_err("verify: failed to canonicalize, " > + verbose(VERB_ALGO, "verify: failed to > canonicalize, " > "rrset too big"); > return 0; > } > @@ -1311,6 +1405,13 @@ rrset_canonical(struct regional* region, > sldns_buffer_write(buf, can_owner, can_owner_len); > else insert_can_owner(buf, k, sig, &can_owner, > &can_owner_len); > + /* Check again, if the rdata can fit in the buffer */ > + if(sldns_buffer_remaining(buf) < 2 + 2 + 4 > + + d->rr_len[walk->rr_idx]) { > + verbose(VERB_ALGO, "verify: failed to > canonicalize, " > + "rrset too big"); > + return 0; > + } > sldns_buffer_write(buf, &k->rk.type, 2); > sldns_buffer_write(buf, &k->rk.rrset_class, 2); > sldns_buffer_write(buf, sig+4, 4); > @@ -1325,10 +1426,11 @@ rrset_canonical(struct regional* region, > * the non-existence proves. */ > if(ntohs(k->rk.type) == LDNS_RR_TYPE_NSEC && > section == LDNS_SECTION_AUTHORITY && qstate) { > - k->rk.dname = regional_alloc_init(qstate->region, > can_owner, > + uint8_t* new_dname = regional_alloc_init(qstate->region, > can_owner, > can_owner_len); > - if(!k->rk.dname) > + if(!new_dname) > return 0; > + k->rk.dname = new_dname; > k->rk.dname_len = can_owner_len; > } > > @@ -1361,11 +1463,17 @@ rrset_canonicalize_to_buffer(struct regi > canonical_sort(k, d, sortree, rrs); > > sldns_buffer_clear(buf); > + if(sldns_buffer_remaining(buf) < k->rk.dname_len) { > + /* Check if the first can_owner name can fit in the > buffer. */ > + verbose(VERB_ALGO, "verify: failed to canonicalize, " > + "rrset too big"); > + return 0; > + } > RBTREE_FOR(walk, struct canon_rr*, sortree) { > /* see if there is enough space left in the buffer */ > if(sldns_buffer_remaining(buf) < can_owner_len + 2 + 2 + 4 > + d->rr_len[walk->rr_idx]) { > - log_err("verify: failed to canonicalize, " > + verbose(VERB_ALGO, "verify: failed to > canonicalize, " > "rrset too big"); > return 0; > } > @@ -1378,6 +1486,13 @@ rrset_canonicalize_to_buffer(struct regi > query_dname_tolower(can_owner); > can_owner_len = k->rk.dname_len; > } > + /* Check again, if the rdata can fit in the buffer */ > + if(sldns_buffer_remaining(buf) < 2 + 2 + 4 > + + d->rr_len[walk->rr_idx]) { > + verbose(VERB_ALGO, "verify: failed to > canonicalize, " > + "rrset too big"); > + return 0; > + } > sldns_buffer_write(buf, &k->rk.type, 2); > sldns_buffer_write(buf, &k->rk.rrset_class, 2); > sldns_buffer_write_u32(buf, d->rr_ttl[walk->rr_idx]); > @@ -1612,6 +1727,30 @@ dnskey_verify_rrset_sig(struct regional* > *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 */ > + } > + } > + /* 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"); > Index: validator/val_sigcrypt.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/val_sigcrypt.h,v > diff -u -p -r1.8 val_sigcrypt.h > --- validator/val_sigcrypt.h 26 May 2026 11:14:11 -0000 1.8 > +++ validator/val_sigcrypt.h 20 Sep 2026 09:50:48 -0000 > @@ -53,6 +53,7 @@ struct ub_packed_rrset_key; > struct rbtree_type; > struct regional; > struct sldns_buffer; > +struct val_qstate; > > /** number of entries in algorithm needs array */ > #define ALGO_NEEDS_MAX 256 > @@ -262,6 +263,7 @@ uint16_t dnskey_get_flags(struct ub_pack > * @param reason_bogus: EDE (RFC8914) code paired with the reason of > failure. > * @param section: section of packet where this rrset comes from. > * @param qstate: qstate with region. > + * @param vq: validator qstate with attempt counts. > * @param verified: if not NULL the number of RRSIG validations is > returned. > * @param reasonbuf: buffer to use for fail reason string print. > * @param reasonlen: length of reasonbuf. > @@ -273,8 +275,9 @@ enum sec_status dnskeyset_verify_rrset(s > struct val_env* ve, struct ub_packed_rrset_key* rrset, > struct ub_packed_rrset_key* dnskey, uint8_t* sigalg, > char** reason, sldns_ede_code *reason_bogus, > - sldns_pkt_section section, struct module_qstate* qstate, int* > verified, > - char* reasonbuf, size_t reasonlen); > + sldns_pkt_section section, struct module_qstate* qstate, > + struct val_qstate* vq, int* verified, char* reasonbuf, > + size_t reasonlen); > > /** > * verify rrset against one specific dnskey (from rrset) > @@ -287,13 +290,16 @@ enum sec_status dnskeyset_verify_rrset(s > * @param reason_bogus: EDE (RFC8914) code paired with the reason of > failure. > * @param section: section of packet where this rrset comes from. > * @param qstate: qstate with region. > + * @param vq: validator qstate with attempt counts. > + * @param num_tagmatches: incremented to keep track of tag matches. > * @return secure if *this* key signs any of the signatures on rrset. > * unchecked on error or and bogus on bad signature. > */ > enum sec_status dnskey_verify_rrset(struct module_env* env, struct > val_env* ve, > struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* > dnskey, > size_t dnskey_idx, char** reason, sldns_ede_code *reason_bogus, > - sldns_pkt_section section, struct module_qstate* qstate); > + sldns_pkt_section section, struct module_qstate* qstate, > + struct val_qstate* vq, size_t* num_tagmatches); > > /** > * verify rrset, with specific dnskey(from set), for a specific rrsig > Index: validator/val_utils.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/val_utils.c,v > diff -u -p -r1.19 val_utils.c > --- validator/val_utils.c 27 Jul 2026 14:14:39 -0000 1.19 > +++ validator/val_utils.c 20 Sep 2026 09:50:48 -0000 > @@ -406,7 +406,8 @@ val_verify_rrset(struct module_env* env, > struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* > keys, > uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, > sldns_pkt_section section, struct module_qstate* qstate, > - int *verified, char* reasonbuf, size_t reasonlen) > + struct val_qstate* vq, int *verified, char* reasonbuf, > + size_t reasonlen) > { > enum sec_status sec; > struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> > @@ -431,7 +432,8 @@ val_verify_rrset(struct module_env* env, > log_nametypeclass(VERB_ALGO, "verify rrset", rrset->rk.dname, > ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); > sec = dnskeyset_verify_rrset(env, ve, rrset, keys, sigalg, reason, > - reason_bogus, section, qstate, verified, reasonbuf, > reasonlen); > + reason_bogus, section, qstate, vq, verified, reasonbuf, > + reasonlen); > verbose(VERB_ALGO, "verify result: %s", sec_status_to_string(sec)); > regional_free_all(env->scratch); > > @@ -475,7 +477,8 @@ val_verify_rrset_entry(struct module_env > struct ub_packed_rrset_key* rrset, struct key_entry_key* kkey, > char** reason, sldns_ede_code *reason_bogus, > sldns_pkt_section section, struct module_qstate* qstate, > - int* verified, char* reasonbuf, size_t reasonlen) > + struct val_qstate* vq, int* verified, char* reasonbuf, > + size_t reasonlen) > { > /* temporary dnskey rrset-key */ > struct ub_packed_rrset_key dnskey; > @@ -489,7 +492,8 @@ val_verify_rrset_entry(struct module_env > dnskey.entry.key = &dnskey; > dnskey.entry.data = kd->rrset_data; > sec = val_verify_rrset(env, ve, rrset, &dnskey, kd->algo, reason, > - reason_bogus, section, qstate, verified, reasonbuf, > reasonlen); > + reason_bogus, section, qstate, vq, verified, reasonbuf, > + reasonlen); > return sec; > } > > @@ -499,13 +503,20 @@ verify_dnskeys_with_ds_rr(struct module_ > struct ub_packed_rrset_key* dnskey_rrset, > struct ub_packed_rrset_key* ds_rrset, size_t ds_idx, char** > reason, > sldns_ede_code *reason_bogus, struct module_qstate* qstate, > - int *nonechecked, char* reasonbuf, size_t reasonlen) > + struct val_qstate* vq, int *nonechecked, char* reasonbuf, > + size_t reasonlen, size_t* num_tagmatches, > + size_t* num_tagmatches_dnskeysig) > { > enum sec_status sec = sec_status_bogus; > size_t i, num, numchecked = 0, numhashok = 0, numsizesupp = 0; > num = rrset_get_count(dnskey_rrset); > *nonechecked = 0; > for(i=0; i + if((*num_tagmatches)++ > MAX_TAG_MATCHES) { > + verbose(VERB_ALGO, "DS match attempt reached " > + "MAX_TAG_MATCHES (%d); bogus", > MAX_TAG_MATCHES); > + return sec_status_bogus; > + } > /* Skip DNSKEYs that don't match the basic criteria. */ > if(ds_get_key_algo(ds_rrset, ds_idx) > != dnskey_get_algo(dnskey_rrset, i) > @@ -518,6 +529,15 @@ verify_dnskeys_with_ds_rr(struct module_ > ds_get_key_algo(ds_rrset, ds_idx), > ds_get_keytag(ds_rrset, ds_idx)); > > + if(vq && vq->num_hash_attempts++ > > env->cfg->val_hash_attempts) { > + *reason = "too many hash attempts"; > + if(reason_bogus) > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + verbose(VERB_ALGO, "rrset failed to verify: too > many hash attempts, " > + "val-hash-attempts (%d); bogus", > env->cfg->val_hash_attempts); > + return sec_status_bogus; > + } > + > /* Convert the candidate DNSKEY into a hash using the > * same DS hash algorithm. */ > if(!ds_digest_match_dnskey(env, dnskey_rrset, i, ds_rrset, > @@ -541,8 +561,14 @@ verify_dnskeys_with_ds_rr(struct module_ > > /* Otherwise, we have a match! Make sure that the DNSKEY > * verifies *with this key* */ > + if(*num_tagmatches_dnskeysig > MAX_TAG_MATCHES) { > + verbose(VERB_ALGO, "DS that matched has too many > DNSKEY to RRSIG tag matches " > + "MAX_TAG_MATCHES (%d); bogus", > MAX_TAG_MATCHES); > + return sec_status_bogus; > + } > sec = dnskey_verify_rrset(env, ve, dnskey_rrset, > dnskey_rrset, > - i, reason, reason_bogus, LDNS_SECTION_ANSWER, > qstate); > + i, reason, reason_bogus, LDNS_SECTION_ANSWER, > qstate, > + vq, num_tagmatches_dnskeysig); > if(sec == sec_status_secure) { > return sec; > } > @@ -586,14 +612,14 @@ val_verify_DNSKEY_with_DS(struct module_ > struct ub_packed_rrset_key* dnskey_rrset, > struct ub_packed_rrset_key* ds_rrset, uint8_t* sigalg, char** > reason, > sldns_ede_code *reason_bogus, struct module_qstate* qstate, > - char* reasonbuf, size_t reasonlen) > + struct val_qstate* vq, char* reasonbuf, size_t reasonlen) > { > /* as long as this is false, we can consider this DS rrset to be > * equivalent to no DS rrset. */ > int has_useful_ds = 0, digest_algo, alg, has_algo_refusal = 0, > nonechecked, has_checked_ds = 0; > struct algo_needs needs; > - size_t i, num; > + size_t i, num, num_tagmatches = 0, num_tagmatches_dnskeysig = 0; > enum sec_status sec; > > if(dnskey_rrset->rk.dname_len != ds_rrset->rk.dname_len || > @@ -615,6 +641,13 @@ val_verify_DNSKEY_with_DS(struct module_ > } > num = rrset_get_count(ds_rrset); > for(i=0; i + if(num_tagmatches > MAX_TAG_MATCHES) { > + verbose(VERB_ALGO, "DS verify attempt reached " > + "MAX_TAG_MATCHES (%d); bogus", > MAX_TAG_MATCHES); > + *reason = "DS verify has too many tag matches"; > + return sec_status_bogus; > + } > + > /* Check to see if we can understand this DS. > * And check it is the strongest digest */ > if(!ds_digest_algo_is_supported(ds_rrset, i) || > @@ -623,9 +656,16 @@ val_verify_DNSKEY_with_DS(struct module_ > continue; > } > > + if(num_tagmatches_dnskeysig > MAX_TAG_MATCHES) { > + verbose(VERB_ALGO, "DS verify attempt reached " > + "DNSKEY to RRSIG MAX_TAG_MATCHES (%d); > bogus", MAX_TAG_MATCHES); > + *reason = "DS verify has too many DNSKEY to RRSIG > tag matches"; > + return sec_status_bogus; > + } > sec = verify_dnskeys_with_ds_rr(env, ve, dnskey_rrset, > - ds_rrset, i, reason, reason_bogus, qstate, > - &nonechecked, reasonbuf, reasonlen); > + ds_rrset, i, reason, reason_bogus, qstate, vq, > + &nonechecked, reasonbuf, reasonlen, > &num_tagmatches, > + &num_tagmatches_dnskeysig); > if(sec == sec_status_insecure) { > /* DNSKEY too large unsupported or algo refused by > * crypto lib. */ > @@ -687,12 +727,12 @@ val_verify_new_DNSKEYs(struct regional* > struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, > struct ub_packed_rrset_key* ds_rrset, int downprot, char** reason, > sldns_ede_code *reason_bogus, struct module_qstate* qstate, > - char* reasonbuf, size_t reasonlen) > + struct val_qstate* vq, char* reasonbuf, size_t reasonlen) > { > uint8_t sigalg[ALGO_NEEDS_MAX+1]; > enum sec_status sec = val_verify_DNSKEY_with_DS(env, ve, > dnskey_rrset, ds_rrset, downprot?sigalg:NULL, reason, > - reason_bogus, qstate, reasonbuf, reasonlen); > + reason_bogus, qstate, vq, reasonbuf, reasonlen); > > if(sec == sec_status_secure) { > return key_entry_create_rrset(region, > @@ -718,14 +758,14 @@ val_verify_DNSKEY_with_TA(struct module_ > struct ub_packed_rrset_key* ta_ds, > struct ub_packed_rrset_key* ta_dnskey, uint8_t* sigalg, char** > reason, > sldns_ede_code *reason_bogus, struct module_qstate* qstate, > - char* reasonbuf, size_t reasonlen) > + struct val_qstate* vq, char* reasonbuf, size_t reasonlen) > { > /* as long as this is false, we can consider this anchor to be > * equivalent to no anchor. */ > int has_useful_ta = 0, digest_algo = 0, alg, has_algo_refusal = 0, > nonechecked, has_checked_ds = 0; > struct algo_needs needs; > - size_t i, num; > + size_t i, num, num_tagmatches = 0, num_tagmatches_dnskeysig = 0; > enum sec_status sec; > > if(ta_ds && (dnskey_rrset->rk.dname_len != ta_ds->rk.dname_len || > @@ -761,6 +801,15 @@ val_verify_DNSKEY_with_TA(struct module_ > if(ta_ds) { > num = rrset_get_count(ta_ds); > for(i=0; i + if(num_tagmatches > MAX_TAG_MATCHES) { > + verbose(VERB_ALGO, "anchor DS verify attempt > reached " > + "MAX_TAG_MATCHES (%d); bogus", > MAX_TAG_MATCHES); > + *reason = "anchor DS verify has too many tag > matches"; > + if(reason_bogus) > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + return sec_status_bogus; > + } > + > /* Check to see if we can understand this DS. > * And check it is the strongest digest */ > if(!ds_digest_algo_is_supported(ta_ds, i) || > @@ -768,9 +817,18 @@ val_verify_DNSKEY_with_TA(struct module_ > ds_get_digest_algo(ta_ds, i) != digest_algo) > continue; > > + if(num_tagmatches_dnskeysig > MAX_TAG_MATCHES) { > + verbose(VERB_ALGO, "anchor DS verify has too many > DNSKEY to RRSIG tag matches " > + "MAX_TAG_MATCHES (%d); bogus", > MAX_TAG_MATCHES); > + *reason = "anchor DS verify has too many DNSKEY to > RRSIG tag matches"; > + if(reason_bogus) > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + return sec_status_bogus; > + } > sec = verify_dnskeys_with_ds_rr(env, ve, dnskey_rrset, > - ta_ds, i, reason, reason_bogus, qstate, > &nonechecked, > - reasonbuf, reasonlen); > + ta_ds, i, reason, reason_bogus, qstate, vq, > + &nonechecked, reasonbuf, reasonlen, > &num_tagmatches, > + &num_tagmatches_dnskeysig); > if(sec == sec_status_insecure) { > has_algo_refusal = 1; > continue; > @@ -813,8 +871,16 @@ val_verify_DNSKEY_with_TA(struct module_ > /* we saw a useful TA */ > has_useful_ta = 1; > > + if(num_tagmatches_dnskeysig > MAX_TAG_MATCHES) { > + verbose(VERB_ALGO, "anchor DS that matched has too > many DNSKEY to RRSIG tag matches " > + "MAX_TAG_MATCHES (%d); bogus", > MAX_TAG_MATCHES); > + *reason = "anchor DS that matched has too many > DNSKEY to RRSIG tag matches"; > + if(reason_bogus) > + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > + return sec_status_bogus; > + } > sec = dnskey_verify_rrset(env, ve, dnskey_rrset, > - ta_dnskey, i, reason, reason_bogus, > LDNS_SECTION_ANSWER, qstate); > + ta_dnskey, i, reason, reason_bogus, > LDNS_SECTION_ANSWER, qstate, vq, &num_tagmatches_dnskeysig); > if(sec == sec_status_secure) { > if(!sigalg || algo_needs_set_secure(&needs, > (uint8_t)dnskey_get_algo(ta_dnskey, i))) { > @@ -862,12 +928,13 @@ val_verify_new_DNSKEYs_with_ta(struct re > struct ub_packed_rrset_key* ta_ds_rrset, > struct ub_packed_rrset_key* ta_dnskey_rrset, int downprot, > char** reason, sldns_ede_code *reason_bogus, > - struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) > + struct module_qstate* qstate, struct val_qstate* vq, char* > reasonbuf, > + size_t reasonlen) > { > uint8_t sigalg[ALGO_NEEDS_MAX+1]; > enum sec_status sec = val_verify_DNSKEY_with_TA(env, ve, > dnskey_rrset, ta_ds_rrset, ta_dnskey_rrset, > - downprot?sigalg:NULL, reason, reason_bogus, qstate, > + downprot?sigalg:NULL, reason, reason_bogus, qstate, vq, > reasonbuf, reasonlen); > > if(sec == sec_status_secure) { > @@ -1052,7 +1119,7 @@ val_fill_reply(struct reply_info* chase, > chase->rrsets[chase->an_numrrsets++] = > orig->rrsets[j]; > chase->rrsets[chase->an_numrrsets++] = > orig->rrsets[i]; > } > - } > + } > /* AUTHORITY section */ > for(i = (skip > orig->an_numrrsets)?skip:orig->an_numrrsets; > ian_numrrsets+orig->ns_numrrsets; > @@ -1086,6 +1153,23 @@ val_fill_reply(struct reply_info* chase, > chase->ar_numrrsets; > } > > +void val_reply_remove_answers(struct reply_info* rep, size_t index, > + size_t count) > +{ > + log_assert(index < rep->rrset_count); > + log_assert(index < rep->an_numrrsets); > + if(count == 0) > + return; /* nothing to do */ > + log_assert(index+(count-1) < rep->rrset_count); > + log_assert(index+(count-1) < rep->an_numrrsets); > + if(rep->rrset_count - (count-1) - index - 1 > 0) > + memmove(rep->rrsets+index, rep->rrsets+index+(count-1)+1, > + sizeof(struct ub_packed_rrset_key*)* > + (rep->rrset_count - (count-1) - index - 1)); > + rep->an_numrrsets -= count; > + rep->rrset_count -= count; > +} > + > void val_reply_remove_auth(struct reply_info* rep, size_t index) > { > log_assert(index < rep->rrset_count); > @@ -1319,10 +1403,11 @@ val_find_DS(struct module_env* env, uint > /* DS rrset exists. Return it to the validator > immediately*/ > struct ub_packed_rrset_key* copy = > packed_rrset_copy_region( > rrset, region, *env->now); > - struct packed_rrset_data* d = copy->entry.data; > + struct packed_rrset_data* d; > lock_rw_unlock(&rrset->entry.lock); > if(!copy) > return NULL; > + d = (struct packed_rrset_data*)copy->entry.data; > msg = dns_msg_create(nm, nmlen, LDNS_RR_TYPE_DS, c, > region, 1); > if(!msg) > return NULL; > @@ -1365,4 +1450,21 @@ int derive_cname_from_dname(struct ub_pa > memmove(out, cname->rk.dname, prefix_len); > 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); > } > Index: validator/val_utils.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/val_utils.h,v > diff -u -p -r1.11 val_utils.h > --- validator/val_utils.h 27 Jul 2026 14:14:39 -0000 1.11 > +++ validator/val_utils.h 20 Sep 2026 09:50:48 -0000 > @@ -55,6 +55,11 @@ struct regional; > struct val_anchors; > struct rrset_cache; > struct sock_list; > +struct val_qstate; > + > +/** Maximum number of matches with key tag and algorithm, for DNSKEY to > + * RRSIG and DS to DNSKEY. Since the number is O(N*N), there is a limit. > */ > +#define MAX_TAG_MATCHES 256 > > /** > * Response classifications for the validator. The different types of > proofs. > @@ -124,6 +129,7 @@ void val_find_signer(enum val_classifica > * @param reason_bogus: EDE (RFC8914) code paired with the reason of > failure. > * @param section: section of packet where this rrset comes from. > * @param qstate: qstate with region. > + * @param vq: validator qstate with attempt counts. > * @param verified: if not NULL, the number of RRSIG validations is > returned. > * @param reasonbuf: buffer to use for fail reason string print. > * @param reasonlen: length of reasonbuf. > @@ -133,7 +139,8 @@ enum sec_status val_verify_rrset_entry(s > struct val_env* ve, struct ub_packed_rrset_key* rrset, > struct key_entry_key* kkey, char** reason, sldns_ede_code > *reason_bogus, > sldns_pkt_section section, struct module_qstate* qstate, > - int* verified, char* reasonbuf, size_t reasonlen); > + struct val_qstate* vq, int* verified, char* reasonbuf, > + size_t reasonlen); > > /** > * Verify DNSKEYs with DS rrset. Like val_verify_new_DNSKEYs but > @@ -148,6 +155,7 @@ enum sec_status val_verify_rrset_entry(s > * @param reason: reason of failure. Fixed string or alloced in scratch. > * @param reason_bogus: EDE (RFC8914) code paired with the reason of > failure. > * @param qstate: qstate with region. > + * @param vq: validator qstate with attempt counts. > * @param reasonbuf: buffer to use for fail reason string print. > * @param reasonlen: length of reasonbuf. > * @return: sec_status_secure if a DS matches. > @@ -158,7 +166,7 @@ enum sec_status val_verify_DNSKEY_with_D > struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, > struct ub_packed_rrset_key* ds_rrset, uint8_t* sigalg, char** reason, > sldns_ede_code *reason_bogus, struct module_qstate* qstate, > - char* reasonbuf, size_t reasonlen); > + struct val_qstate* vq, char* reasonbuf, size_t reasonlen); > > /** > * Verify DNSKEYs with DS and DNSKEY rrset. Like > val_verify_DNSKEY_with_DS > @@ -172,8 +180,9 @@ enum sec_status val_verify_DNSKEY_with_D > * algorithm is enough. The list of signalled algorithms is returned, > * must have enough space for ALGO_NEEDS_MAX+1. > * @param reason: reason of failure. Fixed string or alloced in scratch. > -* @param reason_bogus: EDE (RFC8914) code paired with the reason of > failure. > + * @param reason_bogus: EDE (RFC8914) code paired with the reason of > failure. > * @param qstate: qstate with region. > + * @param vq: validator qstate with attempt counts. > * @param reasonbuf: buffer to use for fail reason string print. > * @param reasonlen: length of reasonbuf. > * @return: sec_status_secure if a DS matches. > @@ -185,7 +194,7 @@ enum sec_status val_verify_DNSKEY_with_T > struct ub_packed_rrset_key* ta_ds, > struct ub_packed_rrset_key* ta_dnskey, uint8_t* sigalg, char** reason, > sldns_ede_code *reason_bogus, struct module_qstate* qstate, > - char* reasonbuf, size_t reasonlen); > + struct val_qstate* vq, char* reasonbuf, size_t reasonlen); > > /** > * Verify new DNSKEYs with DS rrset. The DS contains hash values that > should > @@ -202,6 +211,7 @@ enum sec_status val_verify_DNSKEY_with_T > * @param reason: reason of failure. Fixed string or alloced in scratch. > * @param reason_bogus: EDE (RFC8914) code paired with the reason of > failure. > * @param qstate: qstate with region. > + * @param vq: validator qstate with attempt counts. > * @param reasonbuf: buffer to use for fail reason string print. > * @param reasonlen: length of reasonbuf. > * @return a KeyEntry. This will either contain the now trusted > @@ -219,7 +229,7 @@ struct key_entry_key* val_verify_new_DNS > struct ub_packed_rrset_key* dnskey_rrset, > struct ub_packed_rrset_key* ds_rrset, int downprot, char** reason, > sldns_ede_code *reason_bogus, struct module_qstate* qstate, > - char* reasonbuf, size_t reasonlen); > + struct val_qstate* vq, char* reasonbuf, size_t reasonlen); > > /** > * Verify rrset with trust anchor: DS and DNSKEY rrset. > @@ -235,6 +245,7 @@ struct key_entry_key* val_verify_new_DNS > * @param reason: reason of failure. Fixed string or alloced in scratch. > * @param reason_bogus: EDE (RFC8914) code paired with the reason of > failure. > * @param qstate: qstate with region. > + * @param vq: validator qstate with attempt counts. > * @param reasonbuf: buffer to use for fail reason string print. > * @param reasonlen: length of reasonbuf. > * @return a KeyEntry. This will either contain the now trusted > @@ -253,7 +264,7 @@ struct key_entry_key* val_verify_new_DNS > struct ub_packed_rrset_key* ta_ds_rrset, > struct ub_packed_rrset_key* ta_dnskey_rrset, int downprot, > char** reason, sldns_ede_code *reason_bogus, struct module_qstate* > qstate, > - char* reasonbuf, size_t reasonlen); > + struct val_qstate* vq, char* reasonbuf, size_t reasonlen); > > /** > * Determine if DS rrset is usable for validator or not. > @@ -315,6 +326,16 @@ void val_fill_reply(struct reply_info* c > size_t cname_skip, uint8_t* name, size_t len, uint8_t* signer); > > /** > + * Remove rrsets with index .. index+count from reply, from the answer > section. > + * @param rep: reply to remove it from. > + * @param index: rrset to remove, must be in the answer section. > + * @param count: number of rrsets to remove, starting from the index. > + * with count=1, it removes only the index rrset. > + */ > +void val_reply_remove_answers(struct reply_info* rep, size_t index, > + size_t count); > + > +/** > * Remove rrset with index from reply, from the authority section. > * @param rep: reply to remove it from. > * @param index: rrset to remove, must be in the authority section. > @@ -441,5 +462,8 @@ int derive_cname_from_dname(struct ub_pa > /** 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); > + > +/** 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 */ > Index: validator/validator.c > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/validator.c,v > diff -u -p -r1.30 validator.c > --- validator/validator.c 27 Jul 2026 14:14:39 -0000 1.30 > +++ validator/validator.c 20 Sep 2026 09:50:48 -0000 > @@ -68,6 +68,9 @@ > #define MAX_VALIDATE_AT_ONCE 8 > /** Max number of validation suspends allowed, error out otherwise. */ > #define MAX_VALIDATION_SUSPENDS 16 > +/** Max answer RRsets for qtype ANY that are validated. The lists is > + * shortened to fit this limit. */ > +#define MAX_RRSETS_ANY_VALIDATED 24 > > /* forward decl for cache response and normal super inform calls of a DS > */ > static void process_ds_response(struct module_qstate* qstate, > @@ -347,13 +350,17 @@ static void > val_restart(struct val_qstate* vq) > { > struct comm_timer* temp_timer; > - int restart_count; > + int restart_count, num_validation_attempts, num_hash_attempts; > if(!vq) return; > temp_timer = vq->suspend_timer; > restart_count = vq->restart_count+1; > + num_validation_attempts = vq->num_validation_attempts; > + num_hash_attempts = vq->num_hash_attempts; > memset(vq, 0, sizeof(*vq)); > vq->suspend_timer = temp_timer; > vq->restart_count = restart_count; > + vq->num_validation_attempts = num_validation_attempts; > + vq->num_hash_attempts = num_hash_attempts; > vq->state = VAL_INIT_STATE; > } > > @@ -452,6 +459,24 @@ already_validated(struct dns_msg* ret_ms > return 0; > } > > +/** If it is possible to restart the validation state */ > +static int > +val_can_restart(struct module_qstate* qstate, struct val_qstate* vq, > + struct val_env* ve) > +{ > + /* For validation failures that are limits exceeded on the amount > + * of work that the DNSSEC validator is willing to do, the restart > + * is not allowed. A restart would increase the amount of effort > + * spent even further. */ > + if(vq->restart_count < ve->max_restart && > + vq->num_validation_attempts <= > qstate->env->cfg->val_validation_attempts && > + vq->num_hash_attempts <= > qstate->env->cfg->val_hash_attempts && > + !vq->num_nsec_attempts_exceeded) > + return 1; > + (void)qstate; > + return 0; > +} > + > /** > * Generate a request for DNS data. > * > @@ -760,8 +785,8 @@ validate_msg_signatures(struct module_qs > > /* Verify the answer rrset */ > sec = val_verify_rrset_entry(env, ve, s, key_entry, > &reason, > - &reason_bogus, LDNS_SECTION_ANSWER, qstate, > &verified, > - reasonbuf, sizeof(reasonbuf)); > + &reason_bogus, LDNS_SECTION_ANSWER, qstate, vq, > + &verified, reasonbuf, sizeof(reasonbuf)); > /* If the (answer) rrset failed to validate, then this > * message is BAD. */ > if(sec != sec_status_secure) { > @@ -805,7 +830,7 @@ validate_msg_signatures(struct module_qs > continue; > s = chase_reply->rrsets[i]; > sec = val_verify_rrset_entry(env, ve, s, key_entry, > &reason, > - &reason_bogus, LDNS_SECTION_AUTHORITY, qstate, > + &reason_bogus, LDNS_SECTION_AUTHORITY, qstate, vq, > &verified, reasonbuf, sizeof(reasonbuf)); > /* If anything in the authority section fails to be > secure, > * we have a bad message. */ > @@ -852,7 +877,7 @@ validate_msg_signatures(struct module_qs > if(sname && query_dname_compare(sname, key_entry->name)==0) > (void)val_verify_rrset_entry(env, ve, s, key_entry, > &reason, NULL, LDNS_SECTION_ADDITIONAL, > qstate, > - &verified, reasonbuf, sizeof(reasonbuf)); > + vq, &verified, reasonbuf, > sizeof(reasonbuf)); > /* the additional section can fail to be secure, > * it is optional, check signature in case we need > * to clean the additional section later. */ > @@ -921,10 +946,10 @@ validate_suspend_setup_timer(struct modu > slack += 2; > else if(qstate->env->mesh->all.count >= > qstate->env->mesh->max_reply_states/4) > slack += 1; > - if(vq->suspend_count > 3) > - slack += 3; > - else if(vq->suspend_count > 0) > - slack += vq->suspend_count; > + /* One step of back-off after the first suspend so a single bad > + * message still yields, but does not grow exponentially on its > own. */ > + if(vq->suspend_count > 0) > + slack += 1; > if(slack != 0 && slack <= 12 /* No numeric overflow. */) { > usec = usec << slack; > } > @@ -1025,6 +1050,29 @@ remove_spurious_authority(struct reply_i > } > > /** > + * Cap the number of answer RRsets for validation of type ANY. > + * This limits the number of RRSIG validations performed. > + * It is allowed to return a subset of available RRsets when processing > + * ANY query. > + * @param chase_reply: the chased reply, shorten if if too long. > + * @param orig_reply: original reply, remove the records here as well, > + * so it can be marked as DNSSEC valid. > + * @param skip: the number of rrsets skipped in the answer section due to > + * CNAME chain that is followed. > + * @param max_rrsets: the number allowed. > + */ > +static void > +shorten_answer_any(struct reply_info* chase_reply, > + struct reply_info* orig_reply, size_t skip, size_t max_rrsets) > +{ > + if(chase_reply->an_numrrsets > max_rrsets) { > + size_t to_rem = chase_reply->an_numrrsets - max_rrsets; > + val_reply_remove_answers(chase_reply, max_rrsets, to_rem); > + val_reply_remove_answers(orig_reply, skip+max_rrsets, > to_rem); > + } > +} > + > +/** > * Given a "positive" response -- a response that contains an answer to > the > * question, and no CNAME chain, validate this response. > * > @@ -1407,16 +1455,20 @@ validate_nameerror_response(struct modul > * trusted DNSKEY rrset that signs this response must already have been > * completed. > * > + * @param env: module env. > * @param chase_reply: answer to validate. > */ > static void > -validate_referral_response(struct reply_info* chase_reply) > +validate_referral_response(struct module_env* env, struct reply_info* > chase_reply) > { > - size_t i; > + size_t i, count; > enum sec_status s; > /* message security equals lowest rrset security */ > chase_reply->security = sec_status_secure; > - for(i=0; irrset_count; i++) { > + if(env->cfg->val_clean_additional) > + count = chase_reply->rrset_count; > + else count = > chase_reply->an_numrrsets+chase_reply->ns_numrrsets; > + for(i=0; i s = ((struct packed_rrset_data*)chase_reply->rrsets[i] > ->entry.data)->security; > if(s < chase_reply->security) > @@ -2283,7 +2335,7 @@ processValidate(struct module_qstate* qs > key_entry_get_reason_bogus(vq->key_entry)); > errinf_ede(qstate, "while building chain of trust", > key_entry_get_reason_bogus(vq->key_entry)); > - if(vq->restart_count >= ve->max_restart) > + if(!val_can_restart(qstate, vq, ve)) > key_cache_insert(ve->kcache, vq->key_entry, > qstate->env->cfg->val_log_level >= 2); > return 1; > @@ -2306,6 +2358,9 @@ processValidate(struct module_qstate* qs > &vq->qchase, vq->orig_msg->rep, vq->rrset_skip); > if(subtype != VAL_CLASS_REFERRAL) > remove_spurious_authority(vq->chase_reply, > vq->orig_msg->rep); > + if(subtype == VAL_CLASS_ANY) > + shorten_answer_any(vq->chase_reply, vq->orig_msg->rep, > + vq->rrset_skip, MAX_RRSETS_ANY_VALIDATED); > > /* check signatures in the message; > * answer and authority must be valid, additional is only checked. > */ > @@ -2428,7 +2483,7 @@ processValidate(struct module_qstate* qs > > case VAL_CLASS_REFERRAL: > verbose(VERB_ALGO, "Validating a referral > response"); > - validate_referral_response(vq->chase_reply); > + validate_referral_response(qstate->env, > vq->chase_reply); > verbose(VERB_DETAIL, "validate(referral): %s", > sec_status_to_string( > vq->chase_reply->security)); > @@ -2502,15 +2557,17 @@ processFinished(struct module_qstate* qs > } > > if(subtype == VAL_CLASS_REFERRAL) { > - /* for a referral, move to next unchecked rrset and check > it*/ > - vq->rrset_skip = val_next_unchecked(vq->orig_msg->rep, > - vq->rrset_skip); > - if(vq->rrset_skip < vq->orig_msg->rep->rrset_count) { > - /* and restart for this rrset */ > - verbose(VERB_ALGO, "validator: go to next rrset"); > - vq->chase_reply->security = sec_status_unchecked; > - vq->state = VAL_INIT_STATE; > - return 1; > + if(qstate->env->cfg->val_clean_additional) { > + /* for a referral, move to next unchecked rrset > and check it*/ > + vq->rrset_skip = > val_next_unchecked(vq->orig_msg->rep, > + vq->rrset_skip); > + if(vq->rrset_skip < > vq->orig_msg->rep->rrset_count) { > + /* and restart for this rrset */ > + verbose(VERB_ALGO, "validator: go to next > rrset"); > + vq->chase_reply->security = > sec_status_unchecked; > + vq->state = VAL_INIT_STATE; > + return 1; > + } > } > /* referral chase is done */ > } > @@ -2555,7 +2612,7 @@ processFinished(struct module_qstate* qs > struct msgreply_entry* e; > > /* see if we can try again to fetch data */ > - if(vq->restart_count < ve->max_restart) { > + if(val_can_restart(qstate, vq, ve)) { > verbose(VERB_ALGO, "validation failed, " > "blacklist and retry to fetch data"); > val_blacklist(&qstate->blacklist, qstate->region, > @@ -2847,6 +2904,7 @@ val_operate(struct module_qstate* qstate > * (this rrset is allocated in the wrong region, not the qstate). > * @param ta: trust anchor. > * @param qstate: qstate that needs key. > + * @param vq: validator qstate. > * @param id: module id. > * @param sub_qstate: the sub query state, that is the lookup that fetched > * the trust anchor data, it contains error information for the > answer. > @@ -2857,8 +2915,8 @@ val_operate(struct module_qstate* qstate > */ > static struct key_entry_key* > primeResponseToKE(struct ub_packed_rrset_key* dnskey_rrset, > - struct trust_anchor* ta, struct module_qstate* qstate, int id, > - struct module_qstate* sub_qstate) > + struct trust_anchor* ta, struct module_qstate* qstate, > + struct val_qstate* vq, int id, struct module_qstate* sub_qstate) > { > struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; > struct key_entry_key* kkey = NULL; > @@ -2898,7 +2956,8 @@ primeResponseToKE(struct ub_packed_rrset > /* attempt to verify with trust anchor DS and DNSKEY */ > kkey = val_verify_new_DNSKEYs_with_ta(qstate->region, qstate->env, > ve, > dnskey_rrset, ta->ds_rrset, ta->dnskey_rrset, downprot, > - &reason, &reason_bogus, qstate, reasonbuf, > sizeof(reasonbuf)); > + &reason, &reason_bogus, qstate, vq, reasonbuf, > + sizeof(reasonbuf)); > if(!kkey) { > log_err("out of memory: verifying prime TA"); > return NULL; > @@ -3011,7 +3070,7 @@ ds_response_to_ke(struct module_qstate* > * bogus, then we are done. */ > sec = val_verify_rrset_entry(qstate->env, ve, ds, > vq->key_entry, &reason, &reason_bogus, > - LDNS_SECTION_ANSWER, qstate, &verified, reasonbuf, > + LDNS_SECTION_ANSWER, qstate, vq, &verified, > reasonbuf, > sizeof(reasonbuf)); > if(sec != sec_status_secure) { > verbose(VERB_DETAIL, "DS rrset in DS response did " > @@ -3062,7 +3121,7 @@ ds_response_to_ke(struct module_qstate* > /* Try to prove absence of the DS with NSEC */ > sec = val_nsec_prove_nodata_dsreply( > qstate->env, ve, qinfo, msg->rep, vq->key_entry, > - &proof_ttl, &reason, &reason_bogus, qstate, > + &proof_ttl, &reason, &reason_bogus, qstate, vq, > reasonbuf, sizeof(reasonbuf)); > switch(sec) { > case sec_status_secure: > @@ -3100,7 +3159,7 @@ ds_response_to_ke(struct module_qstate* > sec = nsec3_prove_nods(qstate->env, ve, > msg->rep->rrsets + msg->rep->an_numrrsets, > msg->rep->ns_numrrsets, qinfo, vq->key_entry, > &reason, > - &reason_bogus, qstate, &vq->nsec3_cache_table, > + &reason_bogus, qstate, vq, &vq->nsec3_cache_table, > reasonbuf, sizeof(reasonbuf)); > switch(sec) { > case sec_status_insecure: > @@ -3168,7 +3227,7 @@ ds_response_to_ke(struct module_qstate* > } > sec = val_verify_rrset_entry(qstate->env, ve, cname, > vq->key_entry, &reason, &reason_bogus, > - LDNS_SECTION_ANSWER, qstate, &verified, reasonbuf, > + LDNS_SECTION_ANSWER, qstate, vq, &verified, > reasonbuf, > sizeof(reasonbuf)); > if(sec == sec_status_secure) { > /* Check for wildcard expansion */ > @@ -3289,6 +3348,7 @@ process_ds_response(struct module_qstate > uint8_t* olds = vq->empty_DS_name; > int ret; > *suspend = 0; > + vq->num_nsec_attempts = 0; > vq->empty_DS_name = NULL; > if(sub_qstate && sub_qstate->rpz_applied) { > verbose(VERB_ALGO, "rpz was applied to the DS lookup, " > @@ -3300,6 +3360,8 @@ process_ds_response(struct module_qstate > } > ret = ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske, > sub_qstate); > + /* New NSEC attempt count for next message validation. */ > + vq->num_nsec_attempts = 0; > if(ret != 0) { > switch(ret) { > case 1: > @@ -3341,7 +3403,7 @@ process_ds_response(struct module_qstate > vq->chain_blacklist = NULL; /* fresh blacklist for next > part*/ > /* Keep the forState.state on FINDKEY. */ > } else if(key_entry_isbad(dske) > - && vq->restart_count < ve->max_restart) { > + && val_can_restart(qstate, vq, ve)) { > vq->empty_DS_name = olds; > val_blacklist(&vq->chain_blacklist, qstate->region, > origin, 1); > qstate->errinf = NULL; > @@ -3391,6 +3453,7 @@ process_dnskey_response(struct module_qs > char* reason = NULL; > sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS; > > + vq->num_nsec_attempts = 0; > if(sub_qstate && sub_qstate->rpz_applied) { > verbose(VERB_ALGO, "rpz was applied to the DNSKEY lookup, " > "make it insecure"); > @@ -3410,7 +3473,7 @@ process_dnskey_response(struct module_qs > verbose(VERB_DETAIL, "Missing DNSKEY RRset in response to " > "DNSKEY query."); > > - if(vq->restart_count < ve->max_restart) { > + if(val_can_restart(qstate, vq, ve)) { > val_blacklist(&vq->chain_blacklist, qstate->region, > origin, 1); > qstate->errinf = NULL; > @@ -3447,7 +3510,9 @@ process_dnskey_response(struct module_qs > downprot = qstate->env->cfg->harden_algo_downgrade; > vq->key_entry = val_verify_new_DNSKEYs(qstate->region, qstate->env, > ve, dnskey, vq->ds_rrset, downprot, &reason, &reason_bogus, > - qstate, reasonbuf, sizeof(reasonbuf)); > + qstate, vq, reasonbuf, sizeof(reasonbuf)); > + /* New NSEC attempt count for next message validation. */ > + vq->num_nsec_attempts = 0; > > if(!vq->key_entry) { > log_err("out of memory in verify new DNSKEYs"); > @@ -3458,7 +3523,7 @@ process_dnskey_response(struct module_qs > * state. */ > if(!key_entry_isgood(vq->key_entry)) { > if(key_entry_isbad(vq->key_entry)) { > - if(vq->restart_count < ve->max_restart) { > + if(val_can_restart(qstate, vq, ve)) { > val_blacklist(&vq->chain_blacklist, > qstate->region, origin, 1); > qstate->errinf = NULL; > @@ -3510,6 +3575,7 @@ process_prime_response(struct module_qst > struct trust_anchor* ta = anchor_find(qstate->env->anchors, > vq->trust_anchor_name, vq->trust_anchor_labs, > vq->trust_anchor_len, vq->qchase.qclass); > + vq->num_nsec_attempts = 0; > if(!ta) { > /* trust anchor revoked, restart with less anchors */ > vq->state = VAL_INIT_STATE; > @@ -3528,19 +3594,23 @@ process_prime_response(struct module_qst > > if(ta->autr) { > if(!autr_process_prime(qstate->env, ve, ta, dnskey_rrset, > - qstate)) { > + qstate, vq)) { > + /* New NSEC attempt count for next message > validation. */ > + vq->num_nsec_attempts = 0; > /* trust anchor revoked, restart with less anchors > */ > vq->state = VAL_INIT_STATE; > vq->trust_anchor_name = NULL; > return; > } > } > - vq->key_entry = primeResponseToKE(dnskey_rrset, ta, qstate, id, > + vq->key_entry = primeResponseToKE(dnskey_rrset, ta, qstate, vq, id, > sub_qstate); > lock_basic_unlock(&ta->lock); > + /* New NSEC attempt count for next message validation. */ > + vq->num_nsec_attempts = 0; > if(vq->key_entry) { > if(key_entry_isbad(vq->key_entry) > - && vq->restart_count < ve->max_restart) { > + && val_can_restart(qstate, vq, ve)) { > val_blacklist(&vq->chain_blacklist, > qstate->region, > origin, 1); > qstate->errinf = NULL; > Index: validator/validator.h > =================================================================== > RCS file: /cvs/src/usr.sbin/unbound/validator/validator.h,v > diff -u -p -r1.11 validator.h > --- validator/validator.h 31 Aug 2025 21:41:10 -0000 1.11 > +++ validator/validator.h 20 Sep 2026 09:50:48 -0000 > @@ -231,6 +231,19 @@ struct val_qstate { > struct comm_timer* suspend_timer; > /** Number of suspends */ > int suspend_count; > + > + /** Number of DNSKEY RRSIG validation attempts. This is the number > of > + * cryptographic operations done for the mesh state. */ > + int num_validation_attempts; > + /** Number of DS hash verification attempts. This is the number of > + * hash operations done for the mesh state. > + * It does not count NSEC3 hashes. */ > + int num_hash_attempts; > + /** Number of NSEC validations. And NSEC3 too. This is reset per > + * answer. */ > + int num_nsec_attempts; > + /** The nsec attempts have been exceeded. */ > + int num_nsec_attempts_exceeded; > }; > > /** > > -- Kindest regards, Tom Smyth.