Commit graph

16765 commits

Author SHA1 Message Date
Ondřej Surý
430ce335ad Replace query and client attribute bitfield with named bools
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.
2026-07-02 13:27:11 +02:00
Ondřej Surý
9596a03d21 Reject an RRSIG that covers a signature type
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.
2026-07-02 13:26:48 +02:00
Ondřej Surý
5aa279bbea
Consume the preference before reading the locator in NID/L64/L32 tostruct
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.
2026-07-02 12:16:27 +02:00
Evan Hunt
a7a90eb9d8 Check for secure data before caching CD=1 NXDOMAIN
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.
2026-07-01 23:56:50 -07:00
Mark Andrews
5d7387b185 Add DNS_PRIVATE_BUFFERSIZE and use it
The size of a private records is 1 byte more than the corresponding
NSEC3PARAM record.
2026-07-02 10:08:52 +10:00
Mark Andrews
7182a03b82 Properly detect private records before copying
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.
2026-07-02 10:08:52 +10:00
Ondřej Surý
a957dd13fa Stop leaking DNSSEC keys past the zone key limit
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.
2026-07-01 16:45:56 +10:00
Colin Vidal
d784ab5cd9 Remove useless TTL override in rctx_referral()
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.
2026-07-01 08:40:05 +02:00
Colin Vidal
ff274e37af Delegations have a minimal TTL of 60 seconds
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.
2026-07-01 08:40:05 +02:00
Colin Vidal
dd37b0fdd8 Introduce min/max TTL bounds 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`.
2026-07-01 08:40:05 +02:00
Colin Vidal
bec74c2f41 Naming convention fix for delegdb_lookup
As `dns__delegdb_lookup()` is static and thus not used internally by
other modules of `libdns`, it can be renamed `delegdb_lookup()`.
2026-07-01 08:40:05 +02:00
Ondřej Surý
3e848b2541 Replace uint with unsigned int in the histo.c unit
Similarly, uint is not a standard type, replace it with standard
unsigned int type.
2026-07-01 08:04:48 +02:00
Ondřej Surý
1ada3dc7b0 Replace u_char remnants with uint8_t
The u_char is BSDism that works, but since we have C11 integer types,
uint8_t is a better fit.
2026-07-01 08:04:48 +02:00
Ondřej Surý
30ad2afa9b
Replace void* data pointers with match enum in radix nodes
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.
2026-07-01 06:42:09 +02:00
Ondřej Surý
99c5820327
Replace RADIX_WALK macro with isc_radix_foreach function
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.
2026-07-01 06:09:45 +02:00
Ondřej Surý
188aa43e48
Make isc_radix_insert, dns_iptable_addprefix/merge return void
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.
2026-07-01 06:09:42 +02:00
Ondřej Surý
fbab7ece77
Clean up radix tree code style
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
2026-07-01 06:09:38 +02:00
Ondřej Surý
fc36519d59
Remove AF_UNSPEC and has_prefix from radix tree
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.
2026-07-01 06:06:10 +02:00
Ondřej Surý
7e9bb91087
Inline prefix into radix tree node
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.
2026-07-01 06:05:55 +02:00
Ondřej Surý
db9d9313c2
Remove prefix refcounting from radix tree
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.
2026-07-01 06:05:52 +02:00
Michal Nowak
0802265776 Track new generic rdata files for header generation
brid, dsync, hhit and wallet were missing from the header
dependency list, so edits to them did not regenerate headers.

Assisted-by: Claude:claude-opus-4-8
2026-06-30 17:03:25 +02:00
Alessio Podda
c2e2776b86 Split inline sync state into a separate struct
The inline sync state is only needed when there is a raw-to-secure sync
in progress, and should be handled "atomically".

This commit creates a separate structure that holds all the inline sync
state to streamline the creation and cleanup.
2026-06-30 12:36:46 +00:00
Alessio Podda
0001066fb6 Rename inline sync related methods
The inline sync related methods and structs retained the old
receive_secure_ prefix. Since there is no async tasks involved anymore
we change the naming scheme to a more descriptive one.
2026-06-30 12:36:46 +00:00
Alessio Podda
158b411667 Fix rss_state leak in receive_secure_serial_cancel
Previously receive_secure_serial_cancel() could leak .rss_state on
inline secure bootstrap or zone shutdown, if a pre-existing DNSSEC
signing pass was paused. This has been fixed.
2026-06-30 12:36:46 +00:00
Alessio Podda
0d95e2575a Serialize zone maintenance requests
Some rndc-driven maintenance operations interact with raw-to-secure
synchronization in the same way as setting NSEC3PARAM did. They are
scheduled via async tasks, which can interleave with raw-to-secure sync
and cause crashes.

To solve this, treat them the same as NSEC3PARAM changes, and extend
the pending NSEC3PARAM change queue to a generic maintenance queue that
rndc can post commands to.

These commands are dispatched by the zone_maintenance() callback, which
delays them if a raw-to-secure sync is in progress.
2026-06-30 12:36:46 +00:00
Alessio Podda
5efad56c16 Fix setnsec3param DNSSEC maintenance ordering
Before this commit, NSEC3PARAM changes were scheduled through an async
callback. This leads to a similar problem of interleaving with an
existing raw-to-secure sync, which is unsound since only one writer
version can be active at a time.

The zone already has a queue of "pending" NSEC3PARAM changes, so the
use of async tasks is also redundant. With this commit, we schedule
NSEC3PARAM changes using the pre-existing queues plus a timer.
2026-06-30 12:36:46 +00:00
Alessio Podda
350134f68d Move inline sync into zone maintenance
Before this commit, synchronization between raw and secure zones ran as
async tasks split into quanta. As a consequence, it could interleave
with some zone maintenance operations.

However, synchronization between raw and secure zones needs to keep the
version open across the full operation. There can be only one writer
version open at a time, which makes any model where synchronization and
maintenance interleave unsound.

To fix that, this commit models raw-to-secure synchronization as an
explicit state machine and moves the synchronization operations inside
the zone_maintenance() callback.

This way, zone maintenance operations can be intercepted so they do not
run on a partially synced secure zone. Pending zone maintenance or sync
operations can also be flushed when a full database reload has happened
on the raw side.
2026-06-30 12:36:46 +00:00
Alessio Podda
f2562548c8 Refactor zone timer time handling 2026-06-30 12:36:46 +00:00
Aydın Mercan
1fc06937cd validate query and response time nanosecs when parsing dnstap
An assertion is triggered inside `isc_time_set` when dnstap-read calls
`dns_dt_parse` on dnstap files with query/response time nanosecond
fields greater than a second.

Avoid the assertion by validating the nanosecond fields to be subsecond
when parsing.
2026-06-30 12:08:29 +03:00
Matthijs Mekking
19da825b72 Small update to patch
../lib/dns/rdataslab.c
../lib/dns/rdataslab.c:168:3: error: expression result unused; should this cast be to 'void'? [-Werror,-Wunused-value]
  168 |                 (void *)newslab(rdataset, mctx, region, 0, buflen);
      |
2026-06-29 10:00:17 +00:00
Matthijs Mekking
77754dad29 Remove dead code warnings found by scan-build
Small cleanup, fixing the dead code issues found by scan-build.

Patch submitted by Tim Rühsen.
2026-06-29 10:00:17 +00:00
Mark Andrews
123934931f Tighten referral DS acceptance
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.
2026-06-25 15:21:00 +10:00
Evan Hunt
c5f99f3508 Check that an NSEC signer is at or above the name to be validated
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
2026-06-24 21:03:45 +00:00
Ondřej Surý
477130cf8e Add the tcp-reuse-timeout option
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.
2026-06-24 13:52:46 +02:00
Ondřej Surý
febeac215d Keep idle reused outgoing TCP connections under read
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.
2026-06-24 13:52:46 +02:00
Ondřej Surý
2327277f90 Fail the fetch when a response fails the TSIG signature check
A response that failed the signature check with a missing or unexpected
TSIG used to set nextitem, so the resolver kept reading the dispatch for
another response.  When the response was truncated with the TSIG cut off
the end of the wire, no further response ever arrived and the fetch
stalled until resolver-query-timeout.

Treat an unauthenticated response like every other signature-check
failure and finish the fetch immediately.  A response carrying a missing
or bogus TSIG now yields SERVFAIL instead of being skipped in favour of
a later one; the cookie system test that fed a spoofed TSIG response is
updated to expect that.  The unauthenticated data is still never
returned.
2026-06-24 12:18:19 +02:00
Aram Sargsyan
5e12906669 Fix memory leak bug during zone shutdown
The dns_update_signaturesinc() updates zone signatures in chunks,
keeping its current state in 'zone->rss_state'. When a zone shuts
down, the signature update process is canceled, and all the data
in the state is not freed.

Create a new dns_update_state_clear() function which can be called
from dns_zone_free() to free the memory.
2026-06-22 20:51:53 +00:00
Aram Sargsyan
b56e3cd2ee Handle zone shutting down case in receive_secure_serial()
When the zone is shutting down jump straight to cleanup, otherwise
an assertion failure is possible, e.g. because zone->loop can be
already NULL.
2026-06-22 20:51:53 +00:00
Aram Sargsyan
fad2c5b757 Remove the unused link from 'struct rss'
The link is declared but never used. Remove it.
2026-06-22 20:51:53 +00:00
Aram Sargsyan
5118f3bad6 Fix a bug in "receive secure serial" processing
When a DNS UPDATE messages is received, the zone_send_secureserial()
function can schedule a new receive_secure_serial() call with a new
'rss' object before a previous one had a chance to be fully processed.
This can cause an assertion failure in receive_secure_serial() with a
new 'rss' object (when the old one was rescheduled because it got
DNS_R_CONTINUE from dns_update_signaturesinc()). In other words:

1. receive_secure_serial() called with rss, sets zone->rss = rss,
   reschedules because of DNS_R_CONTINUE
2. receive_secure_serial() called with rss_new, INSIST fails because
   zone->rss != rss_new), i.e. this was called before the old 'rss'
   was fully processed

Change the code logic by introducing a new 'rss_next' field and making
sure the old 'rss' is complete before starting processing the new
one.
2026-06-22 20:51:53 +00:00
Aram Sargsyan
7d7130f51a Remove redundant INSIST
The check is already performed in the INSIST above.
2026-06-22 20:51:53 +00:00
Matthijs Mekking
e7b5be8f84 Fix double initialization in copy_tuple()
Small cleanup. Found with ninja -C build-dir/ scan-build.

Patch submitted by Tim Rühsen.
2026-06-22 14:18:59 +00:00
Ondřej Surý
057042304d
Keep in-flight cache headers safe from LRU eviction during add
When the cache is over its size limit, qpcache_miss() runs LRU eviction
while add() is still in progress.  Eviction removes a header together
with its RRSIG/covered 'related' partner, so it could free a header the
add still needs -- the new header, the partner it was just paired with,
or the one about to be displaced for max-types-per-name.  With 'related'
now a counted reference, that became a use-after-free reachable under
sustained load.

Run the eviction last, after the new header is linked, bound and any
over-limit header removed, and skip the new header and its partner in
the eviction loop (the partner is marked visited so the SIEVE hand still
advances).  Per-header removal is factored into header_delete(),
expire_header() and flush_node().
2026-06-22 13:45:23 +02:00
Ondřej Surý
06abfa3264
Remove the ANCIENT slabheader attribute and statistics counter
Cache headers are now unlinked from their node as soon as they expire,
so a slabheader is never left in the "ancient, awaiting cleanup" state
that DNS_SLABHEADERATTR_ANCIENT tracked.  Drop the attribute, rename
mark_ancient() to header_delete() to reflect that it now removes the
header rather than flagging it (keying idempotency on list membership),
and remove the ancient RRset statistics counter that recorded the state,
which is now always zero; the rdataset statistics array shrinks to
match.

The rdataset-level 'ancient' flag and 'rndc dumpdb -expired' are
unaffected: expiry is derived from the entry's TTL when the rdataset is
bound, not from the slabheader attribute.
2026-06-22 13:45:18 +02:00
Ondřej Surý
5b50f4133a
Factor the header/RRSIG extraction out of the find paths
qpcache_find, qpcache_findrdataset, and find_headers each repeated the
same logic to pick a header and its RRSIG out of a slabheader and its
'related' link.  Pull it into store_headers(), and rename check_header()
to invalid_header() since it returns true when the header should be
skipped.
2026-06-22 13:45:15 +02:00
Ondřej Surý
5bd08f8942
Squash dns_slabtop into dns_slabheader
With headers removed eagerly in mark_ancient there is at most one header
per type at a node, so the separate per-type dns_slabtop container no
longer earns its keep.  Fold its fields onto the header -- the link into
the node's list, the RRSIG/covered related pairing, and the SIEVE-LRU
state -- and link headers directly into the node, dropping a level of
indirection and an allocation per cached type.
2026-06-22 13:45:13 +02:00
Ondřej Surý
76e21399dd
Remove cache headers and empty slabtops eagerly in mark_ancient
Now that a bound rdataset keeps references to both the slabheader and
the node, an ancient header (and the slabtop it leaves empty) can be
removed from the cache immediately in mark_ancient, instead of being
parked on a per-node dirty list and reclaimed only once the node becomes
unreferenced.  This drops the dirty list and the clean_cache_* machinery
entirely.

Because the cache structure can now change under a node that still has
external references, the all-rdatasets iterator no longer walks the live
slabtop list: allrdatasets() binds every matching rdataset up front and
the iterator works from that snapshot, cloning out each entry.
2026-06-22 13:45:08 +02:00
Ondřej Surý
202889b044
Free the slabheader proofs in its destructor
Now that the slabheader carries its own memory context, free its
noqname/closest proofs from slabheader_destroy rather than reaching
through the owning node's deletedata method.  That was the method's last
caller, so remove dns_db_deletedata entirely; the cache bookkeeping it
performed (rrset statistics and the dirty list) becomes a plain helper
called wherever a header leaves the cache.
2026-06-22 13:45:05 +02:00
Ondřej Surý
d9701e1fae
Store the bound node and slabheader reference on the rdataset
The bound rdataset now keeps its own node reference instead of reaching
it through the slabheader, and each slabheader carries its own memory
context so it can free itself once its reference count reaches zero.
The noqname/closest proof rdatasets are views into a slabheader's proof
slabs, so they now hold a reference to that slabheader too, keeping the
proof slab (and the cloned owner name): alive for as long as the proof
rdataset is.  Keeping the node on the rdataset means node access stays
valid for the life of the rdataset, independent of the slabheader's own
node pointer.
2026-06-22 13:44:58 +02:00
Ondřej Surý
db750e75af
Make the dns_slabheaders in the cache reference counted
Instead of only reference counting the enclosing qpcnode, add the
reference counting directly to the slabheaders.  This will allow us to
simplify the data model, as the slabheaders reference is now incremented
when we bind the rdataset and decremented when we disassociate the
rdataset.  This will allow us to remove the slabheader from the slabtop
directly instead of waiting for qpcnode to become completely
unreferenced.
2026-06-22 13:44:55 +02:00