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.
A per-loop work thread referenced the loop manager's memory context,
which is not the context that backs the loop the thread serves. Pass
the owning loop instead and allocate from loop->mctx, keeping a loop
reference for the thread's lifetime. This matches how isc_work_enqueue
and work_done already obtain the context from the loop, and the
teardown uses loop->mctx before dropping the reference.
Offloaded work used two different mechanisms: a per-loop isc_helper
thread for CPU-bound crypto (DNSSEC validation, message signature
checks) and the process-global libuv thread pool for blocking I/O (zone
load and dump, inbound transfer apply). Neither could cancel a queued
task, and the two disagreed about exclusive mode — the helper paused
with its loop under isc_loopmgr_pause() but the libuv pool did not, so
blocking offloaded work kept running while a loop held the exclusive
lock.
Unify both behind isc_work: each loop gets its own worker thread per
lane — FAST for short, bounded tasks and SLOW for long, blocking ones —
fed by a private queue. Separate lanes keep a short crypto task off the
path of a multi-second zone dump once both run on per-loop workers;
every lane parks with isc_loopmgr_pause() so exclusive mode now quiesces
offloaded work too; and a still-queued task can be canceled before it
starts (isc_work_cancel). isc_helper is removed and its callers select a
lane.
The function had a single caller, the HTTP/2 request-path handling in
the network manager, and its semantics (strlen() of the source clamped
to the requested size) amounted to an obscured bounded string copy.
Replace the only use with a plain allocation and strlcpy(), and drop the
function.
A resolver that validated DNSSEC accepted RSA DNSKEYs of any modulus
size up to OpenSSL's compile-time ceiling, and accepted any public
exponent the wire format could carry. RSA verification cost grows
sharply with the modulus length, so an authoritative server could
publish an oversized DNSKEY to make each signature check on the
resolver many times more expensive than for a normally sized key.
The intended verify-time cap had no effect because the helper it called
returned the public-exponent bit length rather than the modulus bit
length, so the test was always satisfied. Replace it with an honest
modulus-range check and a stricter exponent check that accepts only odd
exponents in the closed range [3, 2^32 + 1] (covering every Fermat
prime up to F5 and the odd intermediate values seen in deployed keys),
reject anything outside those bounds at every RSA key load path so an
invalid key never reaches the verifier, and keep the same checks at the
verifier as a backstop against future load paths.
A previous commit introduced a latent bug where the wrong popcount
definition was used when overriding the compilation mode to C23.
This commit fixes it.
When a dst_key_t carries a PKCS#11 URI in key->label (as named
does for dnssec-policy zones backed by a key-store "hsm"), key
generation must happen inside the HSM, not in software.
opensslecdsa_generate already branches on key->label and calls
the matching pkcs11 wrapper; the EDDSA generator silently ignored
the label and produced a software key, which named then wrote to
the .private file with both a Label: line and the raw PrivateKey:
bytes -- a corrupt hybrid record that prevented zone signing.
Add the missing wrapper:
- lib/isc/ossl_wrap/ossl3.c gains generate_pkcs11_eddsa_key()
and the public isc_ossl_wrap_generate_pkcs11_ed25519_key() /
isc_ossl_wrap_generate_pkcs11_ed448_key() entry points. They
use EVP_PKEY_CTX_new_from_name(NULL, "ED25519" or "ED448",
"provider=pkcs11") with the pkcs11_uri and pkcs11_key_usage
parameters, mirroring the existing EC wrapper.
- lib/isc/ossl_wrap/ossl1_1.c provides stubs returning
ISC_R_NOTIMPLEMENTED for the new EDDSA wrappers; the
pkcs11-provider stack requires OpenSSL 3. The pre-existing
isc_ossl_wrap_generate_pkcs11_rsa_key() stub used to silently
delegate to software keygen -- that hid the same "HSM label
on a software key" hazard for RSA on OpenSSL 1.1 builds, so
align it with the EDDSA stubs and return ISC_R_NOTIMPLEMENTED
too.
- lib/isc/include/isc/ossl_wrap.h declares the new wrappers.
- lib/dns/openssleddsa_link.c routes openssleddsa_generate()
through the new wrappers when key->label is non-NULL, leaving
the existing EVP_PKEY_keygen() path untouched for software
keys. The Ed448 case is guarded by HAVE_OPENSSL_ED448 to
match the surrounding code.
Assisted-by: Claude:claude-opus-4-7
SipHash-2-4 was designed as a conservative PRF/MAC with extra rounds
against future attacks. For hash tables, where outputs are never
exposed, SipHash-1-3 provides sufficient collision resistance with
fewer rounds. As the SipHash author noted: "I would be very surprised
if SipHash-1-3 introduced weaknesses for hash tables."
DNS cookies continue to use SipHash-2-4 since cookie values are sent
on the wire and must resist online attacks.
Until now, the dispatcher silently dropped UDP responses from the
expected peer that carried the wrong DNS message id and kept listening
for the correct id to arrive within the read timeout. An off-path
attacker who knows the destination address and source port of an
outgoing fetch could exploit that quiet retry window to flood the
resolver with guessed responses; with a gigabit link the per-query
success probability grows linearly with the number of guesses that
arrive before the legitimate answer or the timeout.
Treat any such mismatch as a possible spoofing attempt and let the
resolver immediately retry the same query over TCP, the same control
path the truncation handler already uses.
Add a resolver statistics counter - exposed as 'queries retried over TCP
after a response with mismatched query id' in rndc stats and
'MismatchTCP' in the statistics channel
Assisted-by: Claude:claude-opus-4-7
The function existence-checked the target with stat() and then opened
the same path without O_NOFOLLOW, so a symlink at the target path
passed the regular-file test against the link's destination and the
open() that followed truncated and wrote through the link.
rndc-confgen -a is typically run as root and writes the keyfile under
a directory that service accounts may have write access to, so a stray
symlink there would silently redirect the truncate, fchown, and
overwrite to whatever file the link pointed at.
Switch the existence check to lstat() and use S_ISREG() so a symlink's
S_IFLNK mode is detected directly (a plain bitmask of S_IFREG matches
both, since S_IFLNK shares its high bit). Add O_NOFOLLOW to both
open() flag sets to close the lstat/open TOCTOU window. Hardening
against unexpected symlinks on intermediate path components is out of
scope.
Assisted-by: Claude:claude-opus-4-7
Enable jemalloc background threads and reduce dirty page decay time from
10s to 1s so that unused memory is returned to the OS sooner. As an
additional safety net, trigger a decay pass after every 16 MiB of frees
(rate-limited to once per second) to handle bursts that the background
thread might not catch in time. On glibc, fall back to malloc_trim(0)
with the same volume-based trigger.
The <sys/endian.h> header has existed in macOS since around ~26. This
causes the `htobeNN`/`htoleNN` macros to be redefined in <isc/endian.h>
in terms of <libkern/OSByteOrder.h> when other system headers include
<sys/endian.h>.
Fix this issue by using checking for the existence of <sys/endian.h> in
meson and including it according to the probe result.
Add an `isc_netaddrlink_t` type wrapping an `isc_netaddr_t` and an
`ISC_LINK`. This enable to build list of `isc_netaddr_t` without
increasing the memory footprint of existing usages of `isc_netaddr_t`
(which doesn't require to be linked).
In the current statistics counter implementation, the statistics are
backed by an array of counters, which are updated via atomic operations.
This leads to contention, especially on high core count
machines.
This commit introduces a new isc_statsmulti_t counter that keeps a
separate array per thread. These counters are then aggregated only when
statistics are queried, shifting work off the critical path.
These changes lead to a ~2% improvement in perflab.
A helper macro that returns the current value of a pointer and sets
it to NULL in one expression, useful for transferring ownership in
designated initializers.
The liburcu rcu_cmpxchg_pointer() uses CMM_RELAXED ordering on the CAS
failure path. When a thread loses the CAS and gets another thread's
pointer back, reading fields through that pointer is a data race on
weakly-ordered architectures (ARM, POWER) because the failing load has
no acquire semantics.
Override rcu_cmpxchg_pointer() and rcu_xchg_pointer() to use standard
__atomic builtins with __ATOMIC_ACQ_REL (success) and __ATOMIC_ACQUIRE
(failure) ordering. This fixes the race on all architectures and is
natively visible to ThreadSanitizer.
We return DNS_R_NOVALIDSIG if we detected a deadlock. Then in
'validate_async_done()', this result value is used to check if we
need to fall back to insecure. As part of that we create a new fetch
but that fails because of the detected deadlock. This results in a loop
of deadlock detected, fallback to insecure, deadlock detected, ...
Add a new result value, ISC_R_DEADLOCK, and return this instead when
we have detected a deadlock. This will be treated as a generic error,
as there is no special handling for this result value.
Use reference counting for isc_histomulti module so that it's
possible to attach/detach to/from the objects when used in the
statistics channel in the coming commits.
NSEC3 hashes are required to fit within a single DNS label. Since there
are 5 bits per label byte without pad characters, the maximum hash size
is floor(63*5/8) (39 bytes).
This patch enforces this maximum length for unknown algorithms, while
strictly enforcing the exact expected digest length for known algorithms
like SHA-1.
For Linux >= 6.8:
Since 2023, Linux has introduced a change to the IP_LOCAL_PORT_RANGE
socket option that eliminates the need for the random window
shifting (implemented as a fallback in the next commit).
By setting IP_LOCAL_PORT_RANGE option, we tell the kernel to use better
approach to the source port selection.
For Linux << 6.8:
This implement selecting port by random shifting range leveraging the
IP_LOCAL_PORT_RANGE socket option. The network manager is initialized
with the ephemeral port range (on startup and on reconfig) and then for
every outgoing TCP connection, we define a custom port range (1000
ports) and then randomly shift the custom range within the system range.
This helps the kernel to reduce the search space to the custom window
between <random_offset, random_offset + 1000>.
Reference:
https://blog.cloudflare.com/linux-transport-protocol-port-selection-performance/#kernel
This is a bit of a namespace convention violation but it fits the spirit of
this header since it is exposing OpenSSL-isms to others.
Further work is needed to make sure the exposed EVP_MD isn't needed
anymore.
Using `EVP_SIGNATURE` explicit algoritms for signatures have been added
in OpenSSL 3.4 and so is skipped for the initial OpenSSL version
specific code splitting.
Using `EVP_SIGNATURE` explicit algoritms for signatures have been added
in OpenSSL 3.4 and so is skipped for the initial OpenSSL version
specific code splitting.
While being the best place at the time, the tlserr2result doesn't belong
inside TLS code since it is generic to OpenSSL and mostly used in the
dst interface. The newly created ossl_wrap interface is the idea place
for flushing the OpenSSL thread error queue.
Instead of the `EVP_MD_CTX` based functions, use either the new
`EVP_MAC` or the old `HMAC_CTX` based functions.
`EVP_MAC` is the recommended way using using MAC functions in post-3.0
while `HMAC_CTX` is used internally by `EVP_MD_CTX`, making the latter
redundant.
Get rid of the OpenSSL-isms that plague the codebase where the hash type
is `EVP_MD *`
By using a proper enum, alongside the cleanup, we also get the ability
to use constants for known hash sizes instead of having a function call
every time.
`EVP_MD_CTX_get0_md` has been removed instead of being adapted since it
wasn't used anymore.
Dealing with OpenSSL has been rapidly turning into an unwieldy situation
as post-3.0 changes turn the library into a different beast.
Start treating pre and post-3.0 versions differently for easier
maintenance.
This adds the following enum isc_one_or_more and isc_zero_or_more
which specify if one or more or zeror or more bytes are required
when reading the unbounded base64 / hex encoded data.
While building on uclibc this error is thrown:
In file included from ./include/dns/log.h:20,
from callbacks.c:19:
../../lib/isc/include/isc/log.h:141:9: error: unknown type name ‘off_t’
141 | off_t maximum_size;
| ^~~~~
This is due to missing include unistd.h, so let's add it on top of
isc/log.h
Signed-off-by: Giulio Benetti <giulio.benetti@benettiengineering.com>
When the CDS/CDNSKEY RRset gets updated, schedule a NOTIFY(CDS) to be
sent to the parental agent. The parental agent is published in the
parent zone as a DSYNC RRset, so first we need to figure out the
parent owner name. This is done by finding the zonecut (querying for
NS RRset until we find a postive answer).
In nsfetch_dsync, we then schedule a zone fetch for the DSYNC record
at <child-labels>._dsync.<parent-labels>. Then we queue the notify
for each target in the DSYNC records that matches the NOTIFY scheme
and CDS RRtype.
Document the way `__attribute__((__constructor__))` and
`__attribute__((__destructor__))` must be used in BIND9 libraries in
order to avoid unexpected behaviors with other third-party libraries.