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.
The get_dnsalg() was just a compatibility layer for 9.18 which lacked
the Algorithm support - remove it in favor of using the .algorithm
property.
In order to properly support private OID algorithms, use the DST value
which is unique across all algorithms.
Also fix private_type_record(): the choice between the 5- and 7-byte
signing record depends on the DST value (256/257 for the private-OID
algorithms), not the on-wire number, which never reaches 256.
Assisted-by: Claude:claude-opus-4-8
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
Replace the unsigned int attributes field in struct ns_query and the unsigned int attributes field in struct ns_client_inner with individual bool bitfields.
Merge branch 'ondrej/query-attributes-to-bitfields' into 'main'
See merge request isc-projects/bind9!11732
Replace the unsigned int attributes field in struct ns_query with
individual bool bitfields. This removes the NS_QUERYATTR_* constants
and the 12 accessor macros (USECACHE, RECURSIONOK, RECURSING, etc.)
from query.c, replacing all bit manipulation with direct bool access.
And replace the unsigned int attributes field in struct ns_client_inner
with individual bool bitfields. This removes the NS_CLIENTATTR_*
constants and the accessor macros (TCP, WANTDNSSEC, etc.), replacing
all bit manipulation with direct bool access.
A recursive resolver could terminate unexpectedly when an authoritative
server returned a crafted RRSIG(RRSIG) record for an insecure zone. Such
records are now rejected.
Closes#6184
Merge branch '6184-reject-rrsig-covering-signature' into 'main'
See merge request isc-projects/bind9!12347
The qpcache_rrsig_any test enumerated only meta-types as the covered
type, so an RRSIG covering RRSIG -- a non-meta signature type -- slipped
through the earlier meta-type hardening. Probe that case too.
Assisted-by: Claude:claude-opus-4-8
An RRSIG whose Type-Covered field is RRSIG is not a meta-type, so it
passed the message parser, and for an insecure domain the resolver
cached it as a standalone signature. The QP cache pairs every RRSIG
header with the non-signature header it covers and never expects the
covered type to itself be a signature, so a signature covering a
signature broke that invariant and aborted named on a crafted response.
Reject the record in the parser, and tighten the cache precondition so
a positive signature header must cover a non-signature type.
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
Merge branch '6097-nid-l64-l32-tostruct-consume' into 'main'
See merge request isc-projects/bind9!12348
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
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.
When a client sent a query with the checking-disabled (CD) bit set and the
answer was NXDOMAIN, the resolver cached that unvalidated negative response and
discarded any DNSSEC-validated records it already held for the same name, even
though the validated data was more trustworthy. A single such response -
including a forged one - could flush validated records from the cache and force
the resolver to fetch them again. The resolver now checks the trust level of the
existing data first and leaves the cache unchanged when it is already validated.
Closes#5877
Merge branch '5877-cd-nxdomain' into 'main'
See merge request isc-projects/bind9!11946
Cache a DNSSEC-validated A record, then make a CD=1 query elicit an
unvalidated NXDOMAIN for the name: the secure RRset must survive, and an
uncached-type query must not get the wrong RRset back.
Assisted-by: Claude:claude-opus-4-8
An unvalidated NXDOMAIN (e.g. from a CD=1 query) marked every RRset at
the name ancient without checking trust, evicting DNSSEC-validated data.
Keep the cache unchanged when any existing RRset is already secure.
dns_ncache_add() now returns DNS_R_UNCHANGED for the rejected add;
negcache() serves a matching cached negative or the queried type, else
SERVFAIL (never the unrelated RRset the add bound), and rctx_ncache()
forwards it so the fetch fails fast.
We were triggering an assertion when trying to copy a private record
to a buffer for modifying. Extend the private type detection and
copy the contents after we have rejected invalid private records.
Closes#5857
Merge branch '5857-properly-detect-private-records-before-copying' into 'main'
See merge request isc-projects/bind9!11816
We were triggering an assertion when trying to copy a private record
to a buffer for modifying. Extend the private type detection and
copy the contents after we have rejected invalid private records.
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
Merge branch '5805-add-clarification-on-non-IN-classes' into 'main'
See merge request isc-projects/bind9!11667
Document that non-IN RR class issues are out of CVE scope
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
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.
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
Merge branch '6051-find-zone-keys-key-leak' into 'main'
See merge request isc-projects/bind9!12328
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
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.
Delegations are now cached with a minimum TTL of 60 seconds by default. Any NS record or A/AAAA glue record with a TTL below this threshold will be raised to 60 seconds.
A new configuration option `min-delegation-ttl` has been added to adjust this limit, or disable it by setting the value to `0`. The corresponding `max-delegation-ttl` option allows the user to configure a maximum TTL for delegations; it is disabled by default.
Closes#6031
Merge branch '6031-delegdb-ttl' into 'main'
See merge request isc-projects/bind9!12102
Function `rctx_referral()` used to force the TTL of an NS record with
TTL 0 to be 1. This has now been removed, as the delegation database
already forces a minimum hard-coded TTL of 1 when the delegation has
a TTL of 0.
Add a system test which covers the behavior of the `min-delegation-ttl`
and `max-delegation-ttl` options (including default, disabling, and
enforcing that min- must be strictly less than max-).
Delegations are now stored in delegdb with a TTL of at least 60 seconds
by default. A new configuration option `min-delegation-ttl` allows
overriding this value or disabling entirely it with `0`.
This hardens the resolver against misconfigured glue or NS records
with very low TTLs, which would otherwise trigger delegation refetches
too often.
A new option `max-delegation-ttl` (which default to `0`) is also added,
enabling an operator to enforce a maximum TTL check for delegations.
The TTL of cached delegations can now have a minimum bound and a maximum
bound. By default, delegdb does not enable TTL bound checking, but this
can be configured from the caller using `dns_delegdb_config_t`.
Replace u_char with uint8_t and uint with unsigned int.
Merge branch 'ondrej/replace-u_char-with-uint8_t' into 'main'
See merge request isc-projects/bind9!12331
Refactor the radix tree used for ACL IP prefix matching, originally imported
from the MRT routing toolkit in 1999 and never modernized. Node size drops
from 100+ bytes across two allocations to 64 bytes in a single cache line.
Expand the unit test suite.
Merge branch 'ondrej/refactor-isc_radix-unit' into 'main'
See merge request isc-projects/bind9!11731
The node data[] array only ever held pointers to two static bools
(dns_iptable_pos/neg). Replace with isc_radix_match_t enum
(RADIX_UNSET/RADIX_ALLOW/RADIX_DENY) stored directly in the node.
This eliminates the void* casts, the static bool variables, the
isc_radix_destroyfunc_t callback (always NULL), and shrinks
isc_radix_node_t from 80 to 64 bytes. Also use sa_family_t for
the prefix family field.
Replace the RADIX_WALK/RADIX_WALK_END macro pair (which injected
hidden variables and split a do/while block across two macros)
with a proper isc_radix_foreach() function that takes a callback
and user argument.
This also eliminates the insecure_prefix_found global variable
and its associated mutex in acl.c -- the foreach argument replaces
both.
isc_radix_insert can no longer fail: node allocation uses
isc_mem_get which aborts on OOM, and prefix copying was eliminated
by inlining. Propagate the void return through dns_iptable_addprefix,
dns_iptable_merge, dns_acl_any, dns_acl_none, and all their callers.
Improve the radix tree code:
- Rename node children from .l/.r to .left/.right
- Rename static functions to drop reserved leading underscores
- Rename legacy macro variables (Xrn, Xsp, Xstack) to
readable names (cur, sp, stack)
- Narrow variable scopes to point of first use
- Use size_t for loop iterators, uint8_t for byte values
- Replace do/while(0) with plain blocks in macros
- Clean up unit test with helpers and meaningful data values
Instead of treating AF_UNSPEC as a special "any" prefix that sets
both IPv4 and IPv6 data slots, insert two separate entries (one
per address family). Both land on the same 0/0 node via the
existing dual-family data[] mechanism.
This eliminates AF_UNSPEC handling from isc_radix_insert (4 branch
blocks removed), removes the has_prefix bool from isc_radix_node_t
(using family==0 for glue nodes instead), and simplifies the
NETADDR_TO_PREFIX_T macro.
Embed isc_prefix_t directly in isc_radix_node_t instead of heap-
allocating it separately. This eliminates per-node isc_mem_get/put
and isc_mem_attach/detach calls, removes a pointer dereference on
every search comparison, and simplifies the code by removing the
_new_prefix, _ref_prefix, and _deref_prefix helpers entirely.
Remove isc_mem_t from isc_prefix_t since it is now a plain value
type with no memory management. Remove per-node isc_mem_t since
nodes use the tree's memory context. Reorder struct fields to
eliminate padding holes.
Radix tree prefixes were reference-counted to allow sharing between
nodes, with refcount==0 used as a sentinel for stack-allocated
prefixes. Since the radix tree is only modified during config
parsing (single-threaded) and read-only during query processing,
the sharing optimization is unnecessary. Always copy prefixes
instead, eliminating the refcount field and the sentinel hack.
Fail at meson configure if an rdata source file in lib/dns/rdata is not
registered in dns_header_depfiles, so edits no longer silently skip
header regeneration (as happened with brid, dsync, hhit, and wallet).
Assisted-by: Claude:claude-opus-4-8
Merge branch 'mnowak/check-rdata-registration' into 'main'
See merge request isc-projects/bind9!12219
Verify in the misc CI job that every rdata source file under
lib/dns/rdata is an input of the generated lib/dns/code.h, i.e.
registered in dns_header_depfiles. An unregistered file is still
compiled into BIND 9 (gen.c scans the directories directly), but
editing it does not trigger regeneration of code.h.
Assisted-by: Claude:claude-opus-4-8
Quote variable expansions, guard MESON_BUILD_ROOT, and enable
nullglob so paths with spaces and empty globs are handled.
Assisted-by: Claude:claude-opus-4-8