Extend the ZoneKeyFile to read the file-backed private key and return it
in a format suitable for use with dnspython. Add ZoneKey.private_key
property to unify the interface.
(cherry picked from commit ee73ba3bba)
This algorithm is deprecated and not currently used in our system tests,
but it should be in the list of all algorithms.
(cherry picked from commit 596e41553c)
Code move with one change - switch dnskey TTL from 300s (DEFAULT_TTL) to
3600s (DNSKEY_TTL).
Assisted-by: Claude:claude-opus-4-8
(cherry picked from commit 55cd4a1e11)
Make zone.FileZoneKey the single representation of a file-backed key
(typically generated by dnssec-keygen). Move the common key-related
functionality into zone.FileZoneKey, and extend that functionality in
kasp.Key to also add state and timing related operations on top. Remove
duplicate into_ta() function.
Note that is_ksk() is implemented differently for kasp.Key: with the
metadata file available, the KSK status is loaded from that file, as it
indicates the authoritative policy decision which makes the key a KSK.
In zone.FileZoneKey which doesn't work with the metadata file, the KSK
status if inferred from the DNSKEY SEP flag - the best information
available for that class.
Assisted-by: Claude:claude-opus-4-8
(cherry picked from commit 35fc5ce8b4)
Add practical tips about specific handler classes. Mention some good
practices and point developers at existing code written in the desired
manner. Document common pitfalls. Suggest preferred approaches for
splitting up complex response handling code.
Backport of MR !12260
Merge branch 'backport-michal/update-asyncdnsserver-related-test-cookbook-parts-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12352
COOKBOOK.md is supposed to be minimal and heavy on examples, so move the
lengthy section about implementing custom ans.py servers from
COOKBOOK.md to README.md.
(cherry picked from commit edd765a092)
Add practical tips about specific handler classes. Mention some good
practices and point developers at existing code written in the desired
manner. Document common pitfalls. Suggest preferred approaches for
splitting up complex response handling code.
(cherry picked from commit a48e5cd98d)
Under `forward-first` policy in a forwarding zone BIND could accept NS above the forward zone apex from negative responses. This has been fixed.
ISC would like to thank Qifan Zhang, of Palo Alto Networks, for the report.
Closes#5937
Backport of MR !12154
Merge branch 'backport-5937-fix-forward-first-referral-bailiwick-v2-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12278
Reject referrals from root/global forwarders, where there is no narrower
forward-zone apex for name_external() to enforce.
(cherry picked from commit 18f4db8f1b)
Apply the existing name_external() bailiwick check to NS RRsets
processed as referrals in rctx_authority_negative(), and enforce the
same check again in rctx_referral() before caching or following the
delegation.
This prevents a forward-first forwarder from installing a parent
zone-cut above the configured forward zone via an authority-section
NS RRset.
(cherry picked from commit 492d11fa39)
Add a system test covering authority-section NS referrals returned by
configured forwarders under forward first.
The test verifies that a forwarder for fwd.hack cannot install the
parent hack zone cut and redirect resolution for the sibling zone
sibling.hack.
(cherry picked from commit ee34bbd208)
NID, L64, and L32 records were decoded incorrectly when converted into their
parsed structures, because the preference field was not skipped before the
locator.
Closes#6097
Backport of MR !12348
Merge branch 'backport-6097-nid-l64-l32-tostruct-consume-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12349
These types had no entries in the rdata test table, so the
tostruct/fromstruct round-trip in check_struct_conversions() never ran
against them -- which is why the missing preference consume in their
tostruct routines went unnoticed for years. Add text and wire vectors
for all three.
Assisted-by: Claude:claude-fable-5
(cherry picked from commit f28bea94e3)
tostruct_nid() and tostruct_l64() read the 16-bit preference with the
non-consuming uint16_fromregion() and then memmove()'d the whole
remaining region -- still ten octets, still anchored at the preference
-- into the eight-octet nid[]/l64[] arrays. That folded the preference
into the first two locator octets and stored two octets past the end of
the array. tostruct_l32() shares the root cause: it read the 32-bit
locator from the same unconsumed offset, so the value was built from the
preference plus the first two locator octets.
Consume the two preference octets first, matching the sibling
tostruct_lp(), and assert the expected framing on the fixed-size types.
(cherry picked from commit 5aa279bbea)
Applying changes to a signed zone — via DNS UPDATE or the inline-signing
raw-to-secure sync — leaked the surplus keys when the zone's key
directory held more than 32, slowly growing named's memory use.
Closes#6051
Backport of MR !12328
Merge branch 'backport-6051-find-zone-keys-key-leak-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12332
Generate more than DNS_MAXZONEKEYS distinct matching private keys for a
zone and call find_zone_keys() through it. The keys past the limit must
be released; the default memory context's leak check, armed with
isc_mem_checkdestroyed(), fails the test if any are abandoned.
Assisted-by: Claude:claude-opus-4-8
(cherry picked from commit 3d0c452cad)
find_zone_keys() collects every matching private key into a local list,
hands the first DNS_MAXZONEKEYS keys to the caller, and frees the rest.
On overflow it destroyed only the first surplus key before breaking out
of the loop, so any keys after it stayed linked on the local list and
were lost when the function returned.
Unlink and destroy every list entry, transferring a key to the caller
only while under the limit. No entry is left behind, so a zone with
more than DNS_MAXZONEKEYS matching keys no longer leaks memory on each
signing attempt.
(cherry picked from commit a957dd13fa)
This adds a note to the security documentation that configuring
resources with non-Internet DNS classes (CHAOS, HESIOD, ...) is not
a supported configuration and could potentially cause issues.
Closes#5805
Backport of MR !11667
Merge branch 'backport-5805-add-clarification-on-non-IN-classes-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12334
CVE-2026-5946 covered assertion failures reachable only through the
handling of resource record classes other than Internet (IN).
Configuring zones or views with such classes is a supported feature;
document in the security assumptions that problems reachable only
through it cannot be the basis for CVE assignment.
Closes#5805
Assisted-by: Claude:claude-opus-4-8
(cherry picked from commit fc06f799dc)
``named`` could hand a new query an idle forwarder/upstream TCP or TLS
connection that the peer had already closed, causing the query to fail
(and CLOSE-WAIT sockets to pile up). Idle reused connections are now
watched, so a close is noticed and the connection is dropped instead of
reused. A new ``tcp-reuse-timeout`` option controls how long an idle
outgoing connection is kept open for reuse (default 5 seconds).
Closes#6171
Backport of MR !8972, !9698, !9927, and !12289
Merge branch 'backport-6171-tcp-reuse-idle-read-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12317
The idle timeout that bounds how long a reused outgoing TCP/TLS
connection is held open for reuse was only tunable through the 'named -T
tcpidletimeout' developer hook added earlier on this branch. Make it a
proper configuration option, tcp-reuse-timeout (options block, in units
of 100 milliseconds like the other tcp-*-timeout options), and drop the
-T hook.
(cherry picked from commit 477130cf8e)
When 'ISC_R_TIMEDOUT' is received in 'tcp_recv()', it times out the
oldest response in the active responses queue, and only after that it
checks whether other active responses have also timed out. So when
setting a timeout value for a read operation after a successful
connection, it makes sense to take the timeout value from the oldest
response in the active queue too, because, theoretically, the responses
can have different timeout values, e.g. when the TCP dispatch is shared.
Currently 'resp' is always NULL. Previously when connect and read
timeouts were not separated in dispatch this affected only logging, but
now since we are setting a new timeout after a successful connection,
we need to choose a suitable response from the active queue.
(cherry picked from commit e61ba5865f)
(partially cherry-picked from commit 64ffbe82c0)
The dns_dispatch_gettcp() function is used for finding an existing TCP
connection that can be reused for sending a query from a specified local
address to a specified remote address. The logic for matching the
provided <local address, remote address> tuple to one of the existing
TCP connections is implemented in the dispatch_match() function:
- if the examined TCP connection already has a libuv handle assigned,
it means the connection has already been established; therefore,
compare the provided <local address, remote address> tuple against
the corresponding address tuple for the libuv handle associated with
the connection,
- if the examined TCP connection does not yet have a libuv handle
assigned, it means the connection has not yet been established;
therefore, compare the provided <local address, remote address>
tuple against the corresponding address tuple that the TCP
connection was originally created for.
This logic limits TCP connection reuse potential as the libuv handle
assigned to an existing dispatch object may have a more specific local
<address, port> tuple associated with it than the local <address, port>
tuple that the dispatch object was originally created for. That's
because the local address for outgoing connections can be set to a
wildcard <address, port> tuple (indicating that the caller does not care
what source <address, port> tuple will be used for establishing the
connection, thereby delegating the task of picking it to the operating
system) and then get "upgraded" to a specific <address, port> tuple when
the socket is bound (and a libuv handle gets associated with it). When
another dns_dispatch_gettcp() caller then tries to look for an existing
TCP connection to the same peer and passes a wildcard address in the
local part of the tuple, the function will not match that request to a
previously-established TCP connection (unless isc_nmhandle_localaddr()
returns a wildcard address as well).
Simplify dispatch_match() so that the libuv handle associated with an
existing dispatch object is not examined for the purpose of matching it
to the provided <local address, remote address> tuple; instead, always
examine the <local address, remote address> tuple that the dispatch
object was originally created for. This enables reuse of TCP
connections created without providing a specific local socket address
while still preventing other connections (created for a specific local
socket address) from being inadvertently shared.
(cherry picked from commit 086c325ad3)
A reused TCP/TLS dispatch with no outstanding responses was left in the
reuse pool with no read pending, so a peer closing the idle connection
went unnoticed: the socket lingered in CLOSE-WAIT and the dead dispatch
was later handed to a new query, which failed and the fetch timed out.
Keep a read pending on an idle connected dispatch, bounded by an idle
timeout, so the close is seen promptly and the connection is dropped
from the pool instead of reused.
The idle read may only be (re)armed while the dispatch is still
connected; arming it on a dispatch that is already shutting down
re-reads a dying handle and double-schedules a netmgr job.
On shutdown, close the connection as soon as the dispatch reaches its
terminal state instead of waiting for the last reference to drop, so an
unexpected read (or a peer-side close) cannot leave the socket in
CLOSE-WAIT while a reference still lingers.
(cherry picked from commit febeac215d)
Named was accepting DS records for sibling zones when it shouldn't
have. This has been fixed.
Closes#5870
Backport of MR !11837
Merge branch 'backport-5870-tighten-referral-ds-acceptance-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12325
Add a system test that verifies the resolver rejects DS records whose
owner name does not match the delegation (NS) name in a referral
response.
A custom authoritative server (ans4) serves the parent zone sibling-ds.
from zone file with delegations for child and sibling subzones. Its
DomainHandler injects a DS record for sibling.sibling-ds into referrals
for child.sibling-ds. The resolver must detect the mismatch, log "DS
doesn't match referral (NS)", and return SERVFAIL.
Assisted-by: Claude:claude-opus-4-8
(cherry picked from commit a2b9dcff54)
The zone template can't use $ORIGIN with AsyncDnsServer because
AsyncDnsServer._load_zone_file_with_origin() rejects it for non-root
zones.
(cherry picked from commit 95a268f119)
Named was accepting DS records for sibling zones when it shouldn't
have. Only DS rrsets that match the delegation name should have
been accepted.
Remove ds_name from struct respctx as it is no longer useful.
(cherry picked from commit 123934931f)
Add a check that an NSEC record being used as a proof of nonexistence
for a given name is not signed by a name lower in the DNS hierarchy than
the one in question.
Closes#5876
Backport of MR !12272
Merge branch 'backport-5876-nsec-signer-above-name-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12324
Add a check that an NSEC record being used as a proof of nonexistence
for a given name is not signed by a name lower in the DNS hierarchy than
the one in question.
Fixes: isc-projects/bind9#5876
(cherry picked from commit c5f99f3508)
Replace the shell and Perl based FORMERR system test with a Python
test that constructs the malformed DNS packets directly and checks
the responses.
Remove the legacy shell and Perl test script and the intermediate
packet files in hex, leaving the packet construction inline in
tests_formerr.py.
Backport of MR !11898
Merge branch 'backport-stepan/formerr-system-test-python-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12270
Normalize the message ID to 0 and the TTL of records to 1 unless
required (OPT and UPDATE records require TTL=0).
Rename the questionclass test case to twoquestionclasses for
consistency.
(cherry picked from commit 62a3804a4c)
Replace the shell and Perl based FORMERR system test with a Python
test that constructs the malformed DNS packets directly and checks
the responses.
Remove the legacy shell and Perl test script and the intermediate
packet files in hex, leaving the packet construction inline in
tests_formerr.py.
Preserve the same wire for all packets sent to the server, but
construct them in a more explicit and readable way.
(cherry picked from commit 151b2d6045)
On Alpine Linux 3.24, GCC 15 with fortify-headers produces a compiler
warning when building bin/nsupdate/nsupdate.c:
In function 'fgets',
inlined from 'get_next_command' at ../bin/nsupdate/nsupdate.c:2414:13:
/usr/include/fortify/stdio.h:48:16: error: 'cmdlinebuf' may be used uninitialized [-Werror=maybe-uninitialized]
48 | return __orig_fgets(__s, __n, __f);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/fortify/stdio.h:42:1: note: in a call to '*fgets' declared with attribute 'access (read_write, 1, 2)' here
42 | _FORTIFY_FN(fgets) char *fgets(char * _FORTIFY_POS0 __s, int __n, FILE *__f)
| ^~~~~~~~~~~
../bin/nsupdate/nsupdate.c:2405:14: note: 'cmdlinebuf' declared here
2405 | char cmdlinebuf[MAXCMD];
| ^~~~~~~~~~
This is a false positive, because fgets() only writes into the buffer;
the fortify-headers wrapper annotates the buffer argument with
'access (read_write, ...)', which makes GCC treat passing an
uninitialized buffer as a read of uninitialized memory.
Initialize the 'cmdlinebuf' buffer anyway to avoid the build error.
Assisted-by: Claude:claude-fable-5
(cherry picked from commit 62670aa1b7)
On FreeBSD, the TCP connect() call can transiently fail with
EADDRINUSE under parallel CI load. The netmgr already retries such
connects (see #3451), but it retries on the same socket, which is
already bound to the same ephemeral source port, so when the
four-tuple is genuinely busy (e.g. in TIME_WAIT) every retry fails
the same way. pipequeries then exits with "request event result:
address in use", leaving raw.1 empty and failing the first check.
All eight requests share a single TCP dispatch, so the failed connect
means no query ever reached ns4 and its cache is still cold. It is
therefore safe to run pipequeries again: a fresh process binds a new
ephemeral port, and the out-of-order check keeps its meaning. Retry
for up to ten attempts, but only on this specific transient error.
Assisted-by: Claude Code:claude-fable-5
Backport of MR !12228
Merge branch 'backport-mnowak/pipelined-retry-transient-eaddrinuse-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12320
On FreeBSD, the TCP connect() call can transiently fail with
EADDRINUSE under parallel CI load. The netmgr already retries such
connects (see #3451), but it retries on the same socket, which is
already bound to the same ephemeral source port, so when the
four-tuple is genuinely busy (e.g. in TIME_WAIT) every retry fails
the same way. pipequeries then exits with "request event result:
address in use", leaving raw.1 empty and failing the first check.
All eight requests share a single TCP dispatch, so the failed connect
means no query ever reached ns4 and its cache is still cold. It is
therefore safe to run pipequeries again: a fresh process binds a new
ephemeral port, and the out-of-order check keeps its meaning. Retry
for up to ten attempts, but only on this specific transient error.
Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-opus-4-8
(cherry picked from commit b9cf877277)
The "running on" line emitted by `named -V` (as well as the startup
log and `rndc status`, which share the same source) now appends the
PRETTY_NAME value from /etc/os-release in parentheses after the uname
output, e.g.:
running on Linux x86_64 6.19.14-... (Fedora Linux 42 (Workstation Edition))
This helps disambiguate environments where the kernel string is not a
reliable indicator of the userspace, such as RHEL clones and
containers whose kernel does not match the host OS.
When /etc/os-release is absent, /usr/lib/os-release is tried as a
fallback per the systemd os-release(5) specification. When neither is
available or no PRETTY_NAME is found, the output is unchanged.
Assisted-by: Claude:claude-opus-4-7
Closes#5334
Backport of MR !12055
Merge branch 'backport-5334-add-os-platform-to-named-V-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12319
The "running on" line emitted by `named -V` (as well as the startup
log and `rndc status`, which share the same source) now appends the
PRETTY_NAME value from /etc/os-release in parentheses after the uname
output, e.g.:
running on Linux x86_64 6.19.14-... (Fedora Linux 42 (Workstation Edition))
This helps disambiguate environments where the kernel string is not a
reliable indicator of the userspace, such as RHEL clones and
containers whose kernel does not match the host OS.
When /etc/os-release is absent, /usr/lib/os-release is tried as a
fallback per the systemd os-release(5) specification. When neither is
available or no PRETTY_NAME is found, the output is unchanged.
Assisted-by: Claude:claude-opus-4-7
(cherry picked from commit b5ca9d3372)
Related #5760
Backport of MR !11808
Merge branch 'backport-nicki/pytest-add-extra-expired-rrsigs-test-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12318
The test verifies that a validating resolver enforces the
max-validations-per-fetch limit when encountering a record with multiple
expired RRSIGs followed by a valid one. One of the records is signed
three times: twice with expired timestamps (to produce two expired
RRSIGs for a.rrsigs-extra-expired/A) and once with valid timestamps,
after which the expired RRSIGs are injected back into the signed zone
file. max-validations-per-fetch is set to 2 via the template variable so
that the third (valid) RRSIG is never reached, causing SERVFAIL.
Assisted-by: Claude:claude-opus-4-8
(cherry picked from commit fe2fea73a4)
In rare cases named could crash while a view was being removed, for example
during reconfiguration or shutdown, as its internal caches were torn down.
This has been fixed.
Closes#6119
Backport of MR !12177
Merge branch 'backport-6119-fix-possible-uaf-when-destroying-dns_badcache-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12190
Eviction of an entry owned by another loop was bounced to that loop via
isc_async_run(), so a queued list removal could run after the cache had
freed its LRU lists. Use a single mutex-guarded LRU list instead, removing
entries synchronously under the lock, and let each entry hold its own
memory-context reference so the RCU free never touches a gone loop.
(cherry picked from commit bb43ecaf51)
Each response served on a JSON statistics endpoint released the wrong
pointer to the JSON library after the response was sent: the response
body string instead of the JSON document. With the current responses
this does not crash named in practice, but the call is incorrect and
can in principle corrupt memory. XML responses are not affected.
Closes#6024
Backport of MR !12068
Merge branch 'backport-6024-statschannel-json-response-invalid-free-9.20' into 'bind-9.20'
See merge request isc-projects/bind9!12274
wrap_jsonfree() called json_object_put() on the response-body buffer
base, which is the JSON string returned by
json_object_to_json_string_ext(), not a struct json_object. The root
object is already passed in as the callback argument; release only
that.
(cherry picked from commit f39089576a)
Hit every JSON statistics endpoint several times. The current code
calls json_object_put() on the response-body string pointer, which
doesn't crash just by accident - the memory position contains large
value from static string.
(cherry picked from commit 9608158d73)