For SIG(0)-signed requests, view matching is offloaded and the request
is finished asynchronously from ns_client_request_continue(), which
passes client->inner.buffer to dns_dt_send(). That buffer aliases the
network manager's receive buffer, only valid during the read callback,
so it may already be freed and reused, producing garbage dnstap frames
(e.g. the "upforwd" sig0-over-DoT test fails with UQ=0).
When the request is offloaded (ns_client_setup_view() returns
DNS_R_WAIT) and dnstap is enabled, copy the request buffer and point
client->inner.buffer at the copy so it survives the asynchronous hop;
free it in ns__client_reset_cb(). When dnstap is disabled there is no
async consumer of the buffer, so detach it from the receive buffer
instead.
Assisted-by: Claude:claude-opus-4-8
Every fuzz target depended on libtest_dep, which forces building the
libbindtest shared library. In a static build (as used by OSS-Fuzz)
that link fails: libbindtest's netmgr wrappers multiply-define symbols
that also live in the static libisc/libns archives, and the static
system libraries are not position independent.
Only fuzz_dns_qp actually uses the qp test helpers, so give it just
tests/libtest/qp.c via the new libtest_qp_dep and drop libtest_dep
from the fuzzers.
Assisted-by: Claude:claude-opus-4-8
Merge branch 'mnowak/fuzz-drop-libbindtest' into 'main'
See merge request isc-projects/bind9!12194
Every fuzz target depended on libtest_dep, which forces building the
libbindtest shared library. In a static build (as used by OSS-Fuzz)
that link fails: libbindtest's netmgr wrappers multiply-define symbols
that also live in the static libisc/libns archives, and the static
system libraries are not position independent.
Only fuzz_dns_qp actually uses the qp test helpers, so give it just
tests/libtest/qp.c via the new libtest_qp_dep and drop libtest_dep
from the fuzzers.
Assisted-by: Claude:claude-opus-4-8
Building the unit tests in the build job ships them in the CI artifact
(+200 MB) and transfers them over the network. Build them in the unit
test job instead.
Git checks the sources out newer than the build tree restored from the
artifact, which would make meson rebuild all of BIND 9 in the unit test
job. Age the sources so the build is treated as up to date and only the
unit tests get compiled.
Assisted-by: Claude:claude-opus-4-8
Merge branch 'mnowak/build-unit-tests-in-unit-job' into 'main'
See merge request isc-projects/bind9!12180
Building the unit tests in the build job ships them in the CI artifact
(+200 MB) and transfers them over the network. Build them in the unit
test job instead.
When Git checks out the sources, their modification times are newer than
the build tree restored from the artifact, so meson would rebuild all of
BIND 9 in the unit test job. Age the tracked sources so the build is
treated as up to date and only the unit tests get compiled.
Assisted-by: Claude:claude-opus-4-8
- Create `isctest.zone.Zone` helper for zone setup (including signing).
- Add `ZoneKey` helpers for both dnssec-keygen managed keys and python-based keys.
- Add `dnssec_py` shared test setup for DNSSEC tests.
- Add the first example - refactor `nsec3_delegations` into a `dnssec_py` test module.
Merge branch 'nicki/pytest-dnssec-py' into 'main'
See merge request isc-projects/bind9!11807
Introduce an abstract ZoneKey base class with two concrete
implementations:
- FileZoneKey wraps a dnssec-keygen-managed key file (kasp.Key).
- PythonZoneKey holds a Python-native keypair for dnspython-based
signing and key operations.
Both share ZoneKey.into_ta() and ZoneKey.is_ksk(). The ZoneKey
abstraction lets Zone.copy_dssets() and Zone.trust_anchors() handle
pure-Python keys without callers needing to know how the key was made.
Assisted-by: Claude:claude-opus-4-8
Rewrite nsec3_delegation/tests_excessive_nsec3_iterations.py as
dnssec_py/tests_nsec3_iter_too_many.py using the isctest.zone helpers.
The test is a reproducer for CVE-2026-1519 [GL#5708]. It sets up a
delegation from nsec3-iter-too-many. (ns2) to an unsigned sub zone
(ns3), signing the parent with NSEC3 at 51 iterations. A validating
resolver (ns9) must use NSEC3 to prove the sub zone is insecure; the
excessive iteration count is logged as a warning. The test verifies that
the query still resolves successfully (insecure, not SERVFAIL) despite
the high iteration count.
Assisted-by: Claude:claude-opus-4-8
Add a new system test directory for DNSSEC tests written in Python,
using the isctest.zone helpers for zone setup rather than shell sign
scripts.
Set up four nameservers:
- ns1: authoritative for the signed root zone
- ns2: authoritative for test zones (primary)
- ns3: authoritative for additional test zones (typically delegations)
- ns9: validating resolver
Zone configuration for ns2 and ns3 is driven by the ``zones`` template
variable via _common/zones.conf.j2, so each test module's bootstrap()
controls which zones those servers load without touching named.conf.
Individual test modules will be added in subsequent commits.
Assisted-by: Claude:claude-opus-4-8
System tests that set up zones — especially DNSSEC tests — require a
chain of common operations: rendering zone files from templates,
generating keys, signing, and propagating DS records to parent zones.
Implement these as methods on isctest.zone.Zone so individual tests
don't need to repeat the logic in shell or ad-hoc Python.
isctest.zone.Zone is a plain class that holds the zone's data and
accumulated state (delegations, keys) alongside the methods that operate
on it. It is intentionally separate from isctest.template.Zone, which
remains a dumb data container for jinja2 template rendering.
Key design points:
- zone.Zone.name is the text form without trailing dot ("." for root);
zone.Zone.dname holds the dns.name.Name for DNS-level operations;
zone.Zone.basename is the filesystem-safe name ("root" for ".").
- filepath_unsigned / filepath_signed are both always available.
filepath returns the appropriate one based on zone.Zone.signed.
- The zones/ subdirectory is the default (subdir="zones"); old-style
tests that place zone files directly in the ns workdir can pass
subdir=None.
- Signing is opt-in via signed=True; configure() auto-detects whether to
generate keys and sign based on this flag, so the same method handles
both signed and unsigned zones.
- delegations and keys are mutable list attributes; callers append to
them before calling configure() rather than threading them through
every call.
Also:
- Add isctest.template.zones() as a bridge from a list of zone.Zone to a
{name: template.Zone} dict suitable for use as the ``zones`` template
variable. template.zones() resolves filepath to the actual zone file
so templates don't need to know whether a zone is signed.
Assisted-by: Claude:claude-opus-4-8
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
Merge branch '6119-fix-possible-uaf-when-destroying-dns_badcache' into 'main'
See merge request isc-projects/bind9!12177
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.
When `fctx_query()` is called, a DTrace probe (if enabled) prints the
fetch context address, the upstream server address and port, and the
latest known SRTT for the server.
Merge branch 'colin/dtrace-resolver-query' into 'main'
See merge request isc-projects/bind9!12010
When `fctx_query()` is called, a DTrace probe (if enabled) prints the
fetch context address, the upstream server address and port, and the
latest known SRTT for the server.
When using the `synthrecord` plugin in reverse mode, if a very long
prefix is configured by the operator such that there is no room to fit
the reversed IP address into a DNS name, `named` could assert. This has
now been fixed. In such situations, an error is logged so the operator
is aware of the problem, and `NXDOMAIN` is answered.
Closes#6115
Merge branch '6115-synthrecord-prefix' into 'main'
See merge request isc-projects/bind9!12173
When using the `synthrecord` plugin in reverse mode, if a very long
prefix is configured by the operator such that there is no room to fit
the reversed IP address into a DNS name, `named` could assert. This has
now been fixed. In such situations, an error is logged so the operator
is aware of the problem, and `NXDOMAIN` is answered.
Add a system test covering the synthrecord in reverse mode with a (too)
long prefix. If the prefix size doesn't leave room to add the reversed
IP address, the attempt to generate a name is aborted, and `NXDOMAIN` is
returned.
This is an internal simplification of the delegation database's memory
management, replacing the per-thread eviction lists and deferred,
cross-thread record cleanup with a single shared eviction list and
immediate cleanup. There is no change to how delegations are cached or
resolved.
Merge branch 'ondrej/delegdb-shared-sieve-lru' into 'main'
See merge request isc-projects/bind9!12181
The delegation database kept one SIEVE LRU list per loop so that node
eviction could run lock-free on each node's owning loop; this required
every node to hold a loop reference and to defer its own destruction to
that loop via isc_async_run(). Move the SIEVE unlink into the QP write
transaction, taking the evicted node directly from dns_qp_deletename(),
which serialises every list mutation under the qpmulti writer lock and
lets a single shared list replace the per-loop arrays. Node and database
teardown are now synchronous.
The QP trie and the SIEVE list are wrapped in a reference-counted holder.
Each node keeps a reference to the holder so it (and its memory context)
stays valid until the node is destroyed, while shutdown drains the SIEVE
and destroys the trie from an RCU callback and frees the holder once the
last node drops its reference. Reuse across a reconfiguration now moves
ownership of the holder to the new view instead of sharing it through a
separate owners counter, so dns_delegdb_reuse() is removed.
Skip updating the `tid_count` value on repeated calls to prevent ThreadSanitizer 'data race'.
Merge branch 'ondrej/skip-repeated-tid_count-update' into 'main'
See merge request isc-projects/bind9!12186
Normally, the tid_count is initialized only once at the beginning of the
application. The only exception is the pattern in the unit test where
isc_loopmgr is repeatedly created and torn down and each creation of
isc_loopmgr_t calls isc__tid_initcount() with the previous value.
ThreadSanitizer sees that as write operation on unprotected memory are
reports this as data race even though the value has not really changed.
This has been fixed by skipping the tid_count value update on repeated
calls.
In some functions implementing RFC 5011 key maintenance, the
results of `dns_rdata_fromstruct()` were not checked. This has been
fixed.
Closes#5982
Merge branch '5982-keyfetch-done' into 'main'
See merge request isc-projects/bind9!12017
`isc__tid_initcount()` was checking that the current number of thread
didn't exceed `ISC_TID_MAX`, not the newly assigned number. This is now
fixed.
Closes#6113
Merge branch '6113-tid-initcount' into 'main'
See merge request isc-projects/bind9!12164
A previous commit introduced a latent bug where the wrong popcount
definition was used when overriding the compilation mode to C23.
This MR fixes it.
Closes#6055
Merge branch '6055-qp-polyfill-fix' into 'main'
See merge request isc-projects/bind9!12165
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.
Commit 515ff3763c ("Simplify reproducible-build CI job") dropped the
-Ddnstap=disabled option from the "meson reprotest" invocation, which
re-introduced a known reproducibility failure:
Build differences detected
File contents differ: buildrepro/libdnstap.a
The job builds with CFLAGS=${CFLAGS_COMMON}, which enables LTO with
-ffat-lto-objects. Fat LTO objects embed GIMPLE bytecode keyed by a
per-compilation random LTO hash, so they are not reproducible run to
run. libdnstap.a is the only static archive in the build, and meson
treats every .a as a final, checked artifact, so the two reprotest
builds disagree on its contents. The shared libraries are unaffected
because final LTO linking re-emits and strips the bytecode.
Restore the -Ddnstap=disabled workaround, along with a comment
explaining the instability. The unrelated -Ddoc=disabled and
-Doptimization=1 options are left dropped, as they were only build-time
speedups and not related to reproducibility.
Assisted-by: Claude:claude-opus-4-8
Merge branch 'mnowak/reprotest-disable-dnstap-lto' into 'main'
See merge request isc-projects/bind9!12161
Commit 515ff3763c ("Simplify reproducible-build CI job") dropped the
-Ddnstap=disabled option from the "meson reprotest" invocation, which
re-introduced a known reproducibility failure:
Build differences detected
File contents differ: buildrepro/libdnstap.a
The job builds with CFLAGS=${CFLAGS_COMMON}, which enables LTO with
-ffat-lto-objects. Fat LTO objects embed GIMPLE bytecode keyed by a
per-compilation random LTO hash, so they are not reproducible run to
run. libdnstap.a is the only static archive in the build, and meson
treats every .a as a final, checked artifact, so the two reprotest
builds disagree on its contents. The shared libraries are unaffected
because final LTO linking re-emits and strips the bytecode.
Restore the -Ddnstap=disabled workaround, along with a comment
explaining the instability. The unrelated -Ddoc=disabled and
-Doptimization=1 options are left dropped, as they were only build-time
speedups and not related to reproducibility.
Assisted-by: Claude:claude-opus-4-8
Drop explicit -D options from meson reprotest invocation
and let it use project defaults.
Assisted-by: Claude:claude-opus-4-7
Merge branch 'mnowak/simplify-reproducible-build-ci' into 'main'
See merge request isc-projects/bind9!12095
The test framework already requires Python 3.10+ (conftest.py raises
RuntimeError if version < 3.10), so skipif(sys.version_info < (3, 7))
can never trigger. Remove the dead markers and now-unused sys imports.
Assisted-by: Claude:claude-opus-4-7
When LMDB was made a required dependency (929eccdfdc), the "LMDB" entry
was removed from features.py and the --with-lmdb flag was removed from
feature-test.c. However, the with_lmdb skip marker in mark.py and its
usage in nzd2nzf were not cleaned up. Since FEATURE_LMDB was no longer
being set, the skip condition became permanently true, silently skipping
the test on every run.
Remove the dead skip marker and update other stale references that still
described LMDB as optional (build docs, addzone test comments).
Assisted-by: Claude:claude-opus-4-7