Compare commits

...
Sign in to create a new pull request.

114 commits

Author SHA1 Message Date
Petr Menšík
1faffc32a8 Remove non-matching version from conflict 2026-08-25 15:06:59 +02:00
Petr Menšík
65771b447c Provide include file bind9/config.h again
It got removed somewhere in repeated rebasing process. Get it packaged
again, bind-dyndb-ldap cannot compile without it.
2026-08-25 14:36:15 +02:00
Petr Menšík
2e2533ed0c Fix plugin path, add alternatives to devel libs
Devel libs detection from bind-dyndb-ldap needs working
_libdir/libdns.so. Provide extra symlink to that. But requires
additional change on bind-dyndb-ldap change to follow those links.
2026-08-25 14:36:15 +02:00
Petr Menšík
8c2399e162 Fix tsig-keygen and named-checkzone
Found out that multiple programs require fixed name and cannot contain
suffixes. This will fix those programs to run as designed and switch
versions. It affects:
- tsig-keygen
- ddns-confgen
- named-checkzone
- named-compilezone

This should make those tools to decide only based or starting part of
the program. Upstream supported suffix .exe, but we need support of
different suffixes. This change should work for both.
2026-08-25 14:36:12 +02:00
Petr Menšík
a859b5175e DNSSEC Validation Bypass via Out-of-Zone NSEC Next Field (CVE-2026-13321)
[9.18] [CVE-2026-13321] sec: usr: Fix DNSSEC validation bypass via out-of-zone NSEC Next Field

A malicious zone with out-of-zone NSEC next owner names can cause a DNSSEC validating resolver to cache such record and, if `synth-from-dnssec` is enabled, to generate negative answers for any zone that is covered by the range.

ISC would like to thank Qifan Zhang of Palo Alto Networks for reporting the issue.

Closes isc-projects/bind9#5873
2026-08-25 13:33:29 +02:00
Petr Menšík
711c5c46ba Unexpected exit with NSEC and NSEC3 both present (CVE-2026-13204)
[9.18] [CVE-2026-13204] fix: usr: Prevent crash from malformed NSEC/NSEC3 response

An assertion could be triggered by an improperly signed NOQNAME proof. This has been fixed.

ISC thanks Qifan Zhang of Palo Alto Networks for reporting the issue.

Closes https://gitlab.isc.org/isc-projects/bind9/-/issues/5985
2026-08-25 13:28:00 +02:00
Petr Menšík
7e9f5e27cc Incorrect acceptance of NSEC3 records (CVE-2026-10723)
[9.18] [CVE-2026-10723] sec: usr: Correct verification of NSEC3 signer name

BIND 9 accepted child-zone NSEC3 records where the first label equals the hash of the parent zone as valid parent-zone closest encloser proofs. This has been fixed.

ISC thanks Qifan Zhang of Palo Alto Networks for reporting the issue.

Closes isc-projects/bind9#5874
2026-08-25 13:22:35 +02:00
Petr Menšík
238ad5761b Cache poisoning via label count discrepancy, RRSIG, wildcards (CVE-2026-11721)
[9.18] [CVE-2026-11721] sec: usr: Invalid signed wildcard records were being accepted

Signed wildcard responses in which the Labels field in the `RRSIG` record was less than the number of labels in the Signer Name field were being incorrectly accepted. This in turn broke `synth-from-dnssec`, which depends on such records being correctly validated. This has been fixed.

ISC thanks Qifan Zhang of Palo Alto Networks for bringing this issue to our attention.

Closes https://gitlab.isc.org/isc-projects/bind9/-/issues/5871
2026-08-25 12:46:33 +02:00
Petr Menšík
a0805bd05d Potential memory usage beyond configured limits (CVE-2026-11622)
[9.18] [CVE-2026-11622] sec: usr: Prevent cache exhaustion under sustained attack

The cache memory can become exhausted with expired entries whose memory
is not released due to a sustained attack on the same DNS name that
prevents the cleanup.  This has been fixed.

Closes: https://gitlab.isc.org/isc-projects/bind9/-/work_items/4760
2026-08-25 12:41:39 +02:00
Petr Menšík
f792799c7d Record ordering based unexpected exit with CNAME or DNAME (CVE-2026-12617)
[9.18] [CVE-2026-12617] sec: usr: Do no assert for some specifics CNAME and DNAME queries

A bug in the resolver's handling of certain cached DNAME and CNAME responses could cause named to trigger an assertion failure and exit. An attacker controlling a domain name and the authoritative DNS server it is hosted on could exploit this behavior to cause a denial-of-service. This vulnerability has been fixed.

ISC thanks Qifan Zhang of Palo Alto Networks for bringing this issue to our attention.

Closes: isc-projects/bind9#5946

[9.18] fix: test: Stabilize the cname_dname_negcache test

The test proved unstable due to timing-related race condition, which could produce both false positive and false negative results. Use a more reliable mechanism to reproduce the issue.

Human-effort-level: low
2026-08-25 12:40:55 +02:00
Petr Menšík
c72c35866e Key Record using PRIVATEDNS algorithm may lead to exit (CVE-2026-10822)
[9.18] [CVE-2026-10822] sec: usr: Fix dns_name_fromwire to record boundaries

Previously, `dns_name_fromwire()` did not honor the record boundary when reading names from the wire, allowing malformed records to be accepted when they should not have been. This has been fixed.

Closes: https://gitlab.isc.org/isc-projects/bind9/-/issues/6004
2026-08-25 12:39:56 +02:00
Petr Menšík
3a99b5a25e Potential wildcard CNAME RPZ policy bypass (CVE-2026-11331)
[9.18] [CVE-2026-11331] sec: usr: Fix handling of rpz CNAME expansion that returns name too long

Previously, if the expansion of a wildcard CNAME RPZ policy resulted in a name that exceeded the length limit, a self referential CNAME and the original address record were returned, allowing the policy to be bypassed.  In branches up to 9.20, this also left query processing in an inconsistent state which could trigger an assertion failure.  We now return a YXDOMAIN response, without the address.

ISC would like to thank Laith Mash'al (0xmshal) for bringing this issue to our attention.

Closes https://gitlab.isc.org/isc-projects/bind9/-/issues/5856
2026-08-25 12:39:30 +02:00
Fedora Release Engineering
55c3f8e6d3 Rebuilt for https://fedoraproject.org/wiki/Fedora_45_Mass_Rebuild 2026-07-15 20:11:13 +00:00
Petr Menšík
fbbe880e06 fixup! Replace direct dependencies with pkgconfig symbolic names 2026-07-13 17:15:29 +02:00
Petr Menšík
211a5b09a6 Switch to _sbindir/alternatives
It is in sbin in CentOS. But on Fedora it is equivalent.
2026-07-13 17:14:34 +02:00
Petr Menšík
046122bd9e Fix ghost man pages suffix
Classic shell glob star does not work on non-existing files. Use
explicit man page suffix. It needs to be defined for use in alternatives
anyway.
2026-07-08 20:43:09 +02:00
Petr Menšík
0049ee45a4 Ghost files do not need explicit touch anymore
Touches for ghost files are not necessary for a long time. Drop extra
touches. Use %attr instead to generate good permissions on such files.

Found at:
https://github.com/rpm-software-management/rpm/discussions/3380
2026-07-08 20:32:23 +02:00
Petr Menšík
78e7e9e5ff Fix filter-{a,aaaa}.so unexpanded with ghost flag
The same trick that worked fine for normal files does not work on ghost
file. %ghost flag seems to require only single file after it. Shell is
not expanded, but used in the name.
2026-07-08 20:10:59 +02:00
Petr Menšík
94cc562d63 Fix bind-devel compat link
It points to the non-existent directory. Correct the path to point to
actually used directory for headers.
2026-07-03 17:49:24 +02:00
Petr Menšík
c42426f289 Replace direct dependencies with pkgconfig symbolic names
In this case it should help with protobuf-c-devel dependency, because it
is now provided by protobuf3-c-devel. But pkgconfig is used by bind
anyway and it should make it more safe.

Break requires onto separate lines, if they are provided by different
packages. Stylistic change only.
2026-07-02 16:24:05 +02:00
Petr Menšík
f4645b66ad Create compat symlink also for bind-devel includes
Try to preserve original _includedir/bind9 directory. Replace it with
alternatives symlink leading to version explicit includes.
2026-07-02 12:54:49 +02:00
Petr Menšík
5cc93baa3a _libexec/generate-rndc-key.sh is used by freeipa
Provide compatibility symlink until it is replaced. Freeipa calls the
original place for script used to generate /etc/rndc.key. Preferred
variant should be "systemctl restart named-setup-rndc.service" command.
Until that is used everywhere, provide compatibility fix.
2026-07-02 12:54:49 +02:00
Petr Menšík
dcf19cde8d Convert to %autorelease and %autochangelog
[skip changelog]
2026-07-01 12:11:07 +02:00
Petr Menšík
a7c81a2e29 Stop using build subdirectory and out-of-tree builds
Multiple variants are not built in single build for a long time. Using
different builddir causes a lot of issues in test suite. We do not need
nor want subdirectory anymore. Use source directory for building and
test running.

Might need additional adjusts in tests repository on running test suite.
2026-06-24 16:05:00 +02:00
Petr Menšík
c84bc8187a Stop explicitly conflicting with original bind package 2026-06-24 15:27:17 +02:00
Petr Menšík
3d0ec51117 Reuse %{dnssec_utils_bin1} instead copy&paste
Take advantage it is defined in single value. Can be reused in %files
section as it is.
2026-06-24 15:27:17 +02:00
Petr Menšík
448bd64587 Seemingly working alternatives including unit files
Support basic systemd unit handling

Fix double inclusion of bind9 include subdirectory

Fix bind-dnssec-utils ghost files

Make permissions match target binaries too.

Do not use alternatives for named-checkzone and named-compilezone

Use alternatives, prefix name with %{upname}-

Add also usage of --initscript %{name}. Not use if named service name
should not be used there instead. Match also remove names.

Define reusable variables with tool names

Use it to define common lists, reuse them multiple times.
Add named-{checkzone,compilezone}. Fix utils man8 typo.
Handle mans and units too.
2026-06-24 15:27:14 +02:00
Petr Menšík
8d6979fcf3 Update systemd unit files with %name and %program_suffix
Move creation of systemd units into build phase. Replace not only %name,
also program_suffix.

Change services to use template for systemd unit files

To execute correctly systemd services, make them templates fixed at the
build time. Still conflicts with potential alternatives.

Solve also filter plugins to have suffix also.
2026-06-24 15:26:57 +02:00
Petr Menšík
3c1d216595 Move binaries to versioned executables spec bump
Only bump release. Provide some explanation in changelog.
bind-chroot package remains conflicting and can be installed only once.
2026-06-24 15:26:20 +02:00
Petr Menšík
743cc89bef Use subdirectory in helper services too
Fixed with actually building version
2026-06-24 15:26:20 +02:00
Petr Menšík
7911863b89 Make binaries contain minor version 2026-06-24 15:25:53 +02:00
Petr Menšík
a166e83cd0 Prepare devel subpackage installable multiple times
Only bind-dyndb-ldap uses devel package. Move includes and libraries to
contain major version. That should allow to install both bind9.18-devel
and bind9.20-devel at the same time. Needed to build multiple
bind-dyndb-ldap plugin versions for different variants.

Adjusted to bind 9.18 main component.
2026-06-24 15:24:31 +02:00
Petr Menšík
c5a273aaa5 Remove /etc/rwtab from the package 2026-06-22 12:57:19 +02:00
Petr Menšík
88cf659c1d Update to 9.18.50 (rhbz#2489833)
Removed Features:

- Remove ineffective TCP fallback after repeated UDP timeouts.

Feature Changes:

- Fall back to TCP on receipt of a UDP response with a mismatched query ID.

Bug Fixes:

- Fix DNS64 owner case after DNAME restart.
- Clear REDIRECT flag when it isn't needed.

This should be final release of BIND 9.18 line. That version is now
marked EOL.

Source: https://downloads.isc.org/isc/bind9/9.18.50/doc/arm/html/notes.html#notes-for-bind-9-18-50
2026-06-22 12:57:17 +02:00
Yaakov Selkowitz
4fc3a0f1fa Rebuilt for openssl 4.0 2026-06-12 16:28:34 -04:00
Petr Menšík
7c5c8f76ad Update to 9.18.49 (rhbz#2480121)
Security Fixes:

- Limit resolver server list size. (CVE-2026-3592)
- Fix GSS-API resource leak. (CVE-2026-3039)
- Disable recursion, UPDATE, and NOTIFY for non-IN views. (CVE-2026-5946)
- Avoid unbounded recursion loop. (CVE-2026-5950)
- Fix outgoing zone transfers' quota issue.

Feature Changes:

- Fix CPU spikes and slow queries when cache approaches memory limit.

Bug Fixes:

- Fix named crash when processing SIG records in dynamic updates.
- Fix rndc modzone behavior for a zone in named.conf.
- Fix zone verification of NSEC3 signed zones.
- Prevent a crash when using both dns64 and filter-aaaa.
- Fixed an assertion failure when processing catalog zones.
- Prevent malicious DNSSEC zones from exhausting validator CPU.
- Fix rndc-confgen aborting on HMAC-SHA-384/512 keys above 512 bits.
- Prevent crafted queries from degrading RRL performance.
- Fix a bug in allow-query/allow-transfer catalog zone custom properties.
- Fix a memory leak issue in catalog zones.
- Fix suppressed missing-glue check in named-checkzone.
- Reject record sets too large to serve in DNS.

Source: https://downloads.isc.org/isc/bind9/9.18.49/doc/arm/html/notes.html#notes-for-bind-9-18-49
2026-05-20 14:05:36 +02:00
Petr Menšík
492a790266 Update to 9.18.48 (rhbz#2453853)
Security Fixes:

- Fix crash when reconfiguring zone update policy during active updates.

Bug Fixes:

- Fix a crash triggered by rndc modzone on a zone from a configuration file.
- Fix a crash triggered by rndc modzone on zone that already existed in NZF file.

Source: https://downloads.isc.org/isc/bind9/9.18.48/doc/arm/html/notes.html#notes-for-bind-9-18-48
2026-04-07 17:39:36 +02:00
Petr Menšík
a3ca8b5f92 Update to 9.18.47 (rhbz#2440561)
Security Fixes:
- Fix unbounded NSEC3 iterations when validating referrals to unsigned delegations. (CVE-2026-1519)

Source: https://downloads.isc.org/isc/bind9/9.18.47/doc/arm/html/notes.html#notes-for-bind-9-18-47
2026-03-25 19:27:51 +01:00
Petr Menšík
3bda563fa3 Update to 9.18.46 (rhbz#2440561)
Bug Fixes:

- Stale answer could be served when following CNAME and failures
  occured. [GL #5751]

https://downloads.isc.org/isc/bind9/9.18.46/doc/arm/html/notes.html#notes-for-bind-9-18-46
2026-03-03 20:03:50 +01:00
Petr Menšík
cc844e18ef Fix changelog date ordering 2026-03-03 19:56:51 +01:00
Petr Menšík
c92030d6aa Add forgotten _libdir/named into bind-chroot tmpfiles
One of libdir directories was forgotten. It is the directory containing
the actual plugin, might cause issues if filter-aaaa.so plugin is used.

Related: RHEL-132054
2026-01-28 11:47:33 +01:00
Petr Menšík
fa6f9dbbd1 Create /var/named directories for bind-chroot
Fixes bind-chroot in Image Mode. Include even subdirectories.

Related: RHEL-132053
2026-01-28 11:44:17 +01:00
Petr Menšík
d822836321 Update to 9.18.44 (rhbz#2431609)
Security Fixes:

- Fix incorrect length checks for BRID and HHIT records. (CVE-2025-13878)

Bug Fixes:

- Allow glue in delegations with QTYPE=ANY.
- Reconfiguring an NSEC3 opt-out zone to NSEC caused the zone to be invalid.

https://downloads.isc.org/isc/bind9/9.18.44/doc/arm/html/notes.html#notes-for-bind-9-18-44
2026-01-22 11:48:45 +01:00
Fedora Release Engineering
159d742c21 Rebuilt for https://fedoraproject.org/wiki/Fedora_44_Mass_Rebuild 2026-01-16 08:20:40 +00:00
Fedora Release Engineering
463e7a3d2d Rebuilt for https://fedoraproject.org/wiki/Fedora_44_Mass_Rebuild 2026-01-16 04:23:48 +00:00
Petr Menšík
aef7570bb8 Update to 9.18.43 (rhbz#2415842)
Bug fixes:

- Adding NSEC3 opt-out records could leave invalid records in chain.
- AMTRELAY type 0 presentation format handling was wrong.

Release 9.18.42 bug fixes:

- Skip unsupported algorithms when looking for a signing key.

https://downloads.isc.org/isc/bind9/9.18.43/doc/arm/html/notes.html#notes-for-bind-9-18-43
2025-12-17 19:07:52 +01:00
Petr Menšík
9f78615eb4 Add test case into dnssec system test for new regression
https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/11195

Related: rhbz#2405830
2025-11-05 12:20:46 +01:00
Petr Menšík
d7ce29383b Fix regression in upstream CVE-2025-8677 fix
https://gitlab.isc.org/isc-projects/bind9/-/issues/5570

Related: rhbz#2405830
2025-11-05 12:17:40 +01:00
Petr Menšík
6853a348fc Update to 9.18.41 (rhbz#2405786, CVE-2025-8677 CVE-2025-40778 CVE-2025-40780)
Security fixes:
- DNSSEC validation fails if matching but invalid DNSKEY is found. (CVE-2025-8677)
- Address various spoofing attacks. (CVE-2025-40778)
- Cache-poisoning due to weak pseudo-random number generator.
  (CVE-2025-40780)

New Features:
- Support for parsing HHIT and BRID records has been added.

Removed Features:
- Deprecate the "tkey-domain" statement.
- Deprecate the "tkey-gssapi-credential" statement.

Bug Fixes:
- Prevent spurious SERVFAILs for certain 0-TTL resource records.
- Missing DNSSEC information when CD bit is set in query.

https://downloads.isc.org/isc/bind9/9.18.41/doc/arm/html/notes.html#notes-for-bind-9-18-41
2025-10-24 15:35:31 +02:00
Petr Menšík
666db3a14e Reference to absolute config names from named.rfc1912.zones
Make the reference to primary source in absolute path, rather than
relative to working directory.

Potentially could start using different files than it used before.

Dot not cherry-pick this into stable branches. Could be considered
breaking change, keep it just in rawhide only.
2025-10-10 19:44:49 +02:00
Petr Menšík
54750e5360 Move named.local* and named.empty into /usr/share/named
Imagemode might have separate /var partition not properly initialized by
package installation. Add creation of compat files into tmpfiles.d
definition.

Move primary place of those files from /var/named to /usr/shared/named, so we
even have some place to symlink them from. Originally it had only copy
in sample documentation, which may not be installed.

These source file should be read-only from named and not modified
anyway. Move them to /usr/share/named as read-only, always present
sources. Change compat symlinks in /var/named to point to them instead
of /etc.

Make the doc files only symlinks to those files too, have them there
just once.

Keep named.ca config file in /etc/named.ca

The rest can stay in _datadir, but named.ca can be potentially updated
and as such should remain in /etc and its modification kept.

Move primary copy into /etc and keep just legacy symlink inside
/var/named. Configuration should stay working if the file were modified.

Related: RHEL-97443
2025-10-10 19:44:35 +02:00
Petr Menšík
4a8dd5ccc3 Fix expectations on idna system test
IDNA tests always redirect output into the file. That means its
behaviour has changed and is now processing IDN input by default and
just disables IDN output by default.

New behaviour when redirected is the same as +idnin +noidnout, but does
not fail hard on input errors.

Related: RHEL-112765
Resolves: rhbz#2324186
2025-10-03 16:18:14 +02:00
Petr Menšík
6800e44134 Remove permanently removed parts from README
[skip changelog]
2025-09-16 19:00:55 +02:00
Adam Williamson
8b224f4fa8 No-change rebuild to fix F43 update 2025-09-10 17:39:08 -07:00
Petr Menšík
4d97d1689a logrotate: skip if empty and remove old variants
Many variants are never built anymore. Clean actions to just those still
shipped. But do not trigger named reload when named.run file is empty.
That is common on freeipa installation, where configuration changes
logging to put it elsewhere. named reload is disruptive because how
bind-dyndb-ldap behaves during reloads. Avoid unnecessary reloads with
visible service disruption.
2025-09-05 18:58:15 +02:00
Petr Menšík
3f3e0c1c58 Decode IDN names on input in all situations in utilities (rhbz#2324186)
Use the same name in dig or host utilities when stdout is not a
terminal. Until now it disabled IDN processing when stdout were not a
terminal. Disable just IDN output in that case and try to decode input
name with IDN. Keep failing in interactive sessions, but send even
undecoded name query when output is redirected.

That should limit new surprises and keep most of behaviour without
changes. But do not break in when input name failed to decode and
it were not trying to decode it before.

Related: RHEL-66172
2025-09-03 18:43:54 +02:00
Petr Menšík
a5e89554c9 Offer up to 13 additional servers records
Downstream only patch for now. Required to work together with Microsoft
DNS server Stub zone entry.

Related: RHEL-84006
Resolves: rhbz#2392413
2025-09-02 12:01:28 +02:00
Petr Menšík
4cc7c36636 Update to 9.18.39 (rhbz#2389765)
New Features:

- Support for parsing the DSYNC record has been added.

Feature Changes:

- Add deprecation warnings for RSASHA1, RSASHA1-NSEC3SHA1, and DS digest type 1.

Bug Fixes:

- Clean enough memory when adding new ADB names/entries under memory pressure.
- Rescan the interfaces again when reconfiguring the server.

https://downloads.isc.org/isc/bind9/9.18.39/doc/arm/html/notes.html#notes-for-bind-9-18-39
2025-08-21 11:48:01 +02:00
Petr Mensik
a324aa90fd Fix rsabigexponent upstream test after pkcs11 provider support
Related: https://issues.redhat.com/browse/RHEL-33729
2025-08-14 16:10:37 +02:00
Petr Mensik
c5b2e69f0c Update to 9.18.38 (rhbz#2367771)
Security Fixes:

- Fix an issue when some specific queries could remain unanswered with serve-stale enabled.

New Features:

- Add support for the CO flag to dig.

Bug fixes:

- Correct the default interface-interval from 60s to 60m.
- Fix a purge-keys bug when using multiple views of a zone.
2025-07-30 12:26:22 +02:00
Petr Mensik
3088af74e3 Fix new added warning 2025-07-29 10:57:11 +02:00
Fedora Release Engineering
2e5ec7e2b8 Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild 2025-07-23 17:41:39 +00:00
psklenar@redhat.com
ed27e3714c fedora CI plans move to gitlab for centos-stream test space https://issues.redhat.com/browse/RHELMISC-13073 2025-06-12 16:09:36 +00:00
Petr Mensik
0b246d18df Add extra protections to places needing absolute names
Insert just potential protection in case Patch32 does not cover all
possible cases.
2025-06-10 18:52:35 +02:00
Petr Mensik
21cc8ba4e6 Prevent name.c:670 attributes assertion failed
Bump changelog.
2025-06-10 16:13:12 +02:00
Petr Mensik
988c021cf3 Prevent name.c:670: REQUIRE((name1->attributes & 0x00000001) == (name2->attributes & 0x00000001)) failed
resume_qmin did not handle special case of recursing query hit
unexpected DNS_R_CNAME result. Change result to SERVFAIL in case
of a zone loaded after the recursion started. That prevents crashing
later in query_setorder, where there is uninitialized foundname compared
with absolute order names.

https://gitlab.isc.org/isc-projects/bind9/-/issues/5357
Related: RHEL-30407
2025-06-10 15:34:02 +02:00
Petr Mensik
ea2fc21f01 Hide warning about unused pin variable
Created by downstream backport of openssl provider support.
2025-04-30 12:57:07 +02:00
Petr Mensik
0f0106cb47 Update to 9.18.36 (rhbz#2346607)
Feature Changes:

- Make TLS data processing more reliable in various network conditions.

Bug Fixes:

- Stop caching lack of EDNS support
- Fix resolver statistics counters for timed-out responses.
- Don’t enforce NOAUTH/NOCONF flags in DNSKEYs.
- Fix inconsistency in CNAME/DNAME handling during resolution.

https://downloads.isc.org/isc/bind9/9.18.36/doc/arm/html/notes.html#notes-for-bind-9-18-36
2025-04-25 14:17:41 +02:00
Petr Menšík
feddafc9cf Deactivate jemalloc linking 2025-03-28 22:44:47 +01:00
Petr Menšík
8bc9ab3888 Improve a bit ability to run testsuite from mockbuild
Intended to be run like:
centpkg mockbuild --with SYSTEMTEST -N --enable-network

Do fail when it does not pass. But allow running tests as root.
2025-03-28 22:44:47 +01:00
Petr Menšík
273ea431d5 Attempt to backport OpenSSL provider support with labels
Should allow PKCS11 provider used from dnssec-keyfromlabel and then from
signing tools or named daemon.

Resolves: rhbz#2123076
2025-03-28 22:44:47 +01:00
Petr Menšík
0b6554ff3d Update to 9.18.35 (rhbz#2346607) 2025-03-26 16:36:36 +01:00
Petr Menšík
f2aec3c2c1 Remove pre scriptlet
It generates to empty if, which fails on invalid syntax.
2025-03-26 16:36:34 +01:00
Petr Menšík
c7c0f860f4 Permanently remove DLZ parts build 2025-02-10 20:08:55 +01:00
Petr Menšík
a28c475b98 Updates to nsupdate TLS backport fixing memory
Properly release memory when TLS is used.
2025-02-10 17:59:37 +01:00
Petr Menšík
201205375b Use common pattern in gitignore for every minor update
Do not need to update .gitignore on every update of version. Use
wildcard for everything from now.
2025-02-10 16:33:43 +01:00
Petr Menšík
c72c74cce0 Update to 9.16.33 (rhbz#2342784)
Security Fixes:

- DNS-over-HTTPS flooding fixes. (CVE-2024-12705)
- Limit additional section processing for large RDATA sets. (CVE-2024-11187)

New Features:

- Add a new option to configure the maximum number of outgoing queries per client request.

Bug Fixes:

- Fix nsupdate hang when processing a large update.
- Fix possible assertion failure when reloading server while processing update policy rules. [GL #5006]
- Fix dnssec-signzone signing non-DNSKEY RRsets with revoked keys.
- Fix improper handling of unknown directives in resolv.conf.

https://downloads.isc.org/isc/bind9/9.18.33/doc/arm/html/notes.html#notes-for-bind-9-18-33
2025-02-02 16:06:38 +01:00
Petr Menšík
071ec07d27 Add sysusers named user creation (rhbz#2105415)
Drop original user creating in favor of sysusers file definition.
2025-02-02 16:06:38 +01:00
Adam Williamson
549048cb0e Bump to -6 and rebuild on mass rebuild tag 2025-01-16 10:30:16 -08:00
Adam Williamson
0cd50aebbd Bump to -5 2025-01-16 10:13:24 -08:00
Adam Williamson
5b3a19a8c2 Add named user and group provides
Now https://src.fedoraproject.org/rpms/rpm/pull-request/61 has
landed, packages that set up users and groups must have user(x)
and group(x) provides. If bind used the mechanism recommended in
the guidelines for creating users and groups, this would happen
automatically, but it does not, so this just adds explicit
Provides as a minimal short-term fix.

Signed-off-by: Adam Williamson <awilliam@redhat.com>
2025-01-16 10:09:55 -08:00
Fedora Release Engineering
a553c9e873 Rebuilt for https://fedoraproject.org/wiki/Fedora_42_Mass_Rebuild 2025-01-16 12:29:30 +00:00
7ec2fb38ca Rebuilt for the bin-sbin merge (2nd attempt)
https://fedoraproject.org/wiki/Changes/Unify_bin_and_sbin
2025-01-13 09:48:53 +01:00
Adam Williamson
e4107d36ce No change sidetag rebuild to include bind-dyndb-ldap 2024-12-20 09:30:39 -08:00
Petr Menšík
a3b1b4c6a2 Add new root key 38696 into package files too
Add KSK 2024 into bind package files too.
2024-12-12 21:25:14 +01:00
Petr Menšík
7d1027aabf Update to 9.18.32 (rhbz#2331675)
- Remove CHANGES file from package

Removed Features:

- Disable DLZ plugins, they are not shipped with bind anymore

New Features:

- new 2024 KSK root key

Feature Changes:

- max-records-per-type and max-types-per-name improved logging when
  reached over their value

And NSEC3 and two dig bug fixes.

https://downloads.isc.org/isc/bind9/9.18.32/doc/arm/html/notes.html#notes-for-bind-9-18-32
2024-12-12 20:58:44 +01:00
Petr Menšík
ad87dca169 Disable PDF for all platforms temproarily
It fails only on s390x, but cannot be disabled just on one arch when the
package is noarch. Disable it until resolved.

Related: rhbz#2332076
2024-12-12 20:03:09 +01:00
Petr Menšík
ca5d3f0131 Disable PDF generation on s390x 2024-12-12 12:30:46 +01:00
Petr Menšík
0dcd8bfd6d Include a test for nsupdate changes 2024-12-09 21:21:54 +01:00
Petr Menšík
87f39d667e Backport nsupdate TLS support
This should add working nsupdate support for -S parameter and some
others in addition.

References:
https://issues.redhat.com/browse/FREEIPA-11706
https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/6751
https://gitlab.isc.org/isc-projects/bind9/-/merge_requests/6752

Resolves: FREEIPA-11706
2024-12-09 21:08:34 +01:00
Petr Menšík
a4f0681ebe Update to 9.18.31 (#2319214)
New Features:

- Added WALLET type.

Feature Changes:

- Allow IXFR-to-AXFR fallback on DNS_R_TOOMANYRECORDS.

And bug fixes.

https://downloads.isc.org/isc/bind9/9.18.31/doc/arm/html/notes.html#notes-for-bind-9-18-31
2024-11-14 22:55:36 +01:00
Petr Menšík
047b52731f Bump obsoleted license version
Resolves: rhbz#2308102
2024-11-14 22:55:33 +01:00
Petr Menšík
ed057b3ada Make OpenSSL engine support optional
Make changelog entry. Engine support is still enabled on Fedora, but
disabled for RHEL builds.
2024-10-08 19:45:04 +02:00
Petr Menšík
f25b47caaa Make OpenSSL engine support optional and disabled
openssl-devel-engine is now needed on rawhide to have engine header
present. Make it enabled by default, but possible to disable built
support for it easy way.

https://fedoraproject.org/wiki/Changes/OpensslDeprecateEngine
2024-10-07 22:56:33 +02:00
Petr Menšík
170a2ffa18 Update to 9.18.30 (rhbz#2306542)
- Print the full path of the working directory in startup log messages.
- Follow the number of CPUs set by taskset/cpuset.
- Verification of the privacy of an EDDSA key was broken.
- Fix algorithm rollover bug when there are two keys with the same keytag.

https://downloads.isc.org/isc/bind9/9.18.30/doc/arm/html/notes.html#notes-for-bind-9-18-30

Update to 9.18.29

Feature Changes:

- Tighten max-recursion-queries and add max-query-restarts configuration statement.
- Raise the log level of priming failures.
- Add a compatibility shim for older libuv versions (< 1.19.0)

Bug Fixes:

Return SERVFAIL for a too long CNAME chain.
- Reconfigure catz member zones during named reconfiguration.
- Update key lifetime and metadata after dnssec-policy reconfiguration.
- Fix generation of 6to4-self name expansion from IPv4 address.
- dig +yaml was producing unexpected and/or invalid YAML. output. [GL #4796] [GL !9214]
- SVBC ALPN text parsing failed to reject zero-length ALPN. [GL #4775] [GL !9210]
- Fix false QNAME minimisation error being reported.
- Fix dig +timeout argument when using +https.

https://downloads.isc.org/isc/bind9/9.18.30/doc/arm/html/notes.html#notes-for-bind-9-18-29
2024-10-05 00:58:14 +02:00
Petr Menšík
9ac8e058af Update to 9.18.29 (rhbz#2306542)
- Tighten max-recursion-queries and add max-query-restarts configuration statement.
- Raise the log level of priming failures.
- Bug fixes

https://downloads.isc.org/isc/bind9/9.18.29/doc/arm/html/notes.html#notes-for-bind-9-18-29
2024-10-05 00:58:14 +02:00
Petr Menšík
891dc1c77c Remove ancient triggerun and selinux boolean traces 2024-10-05 00:58:14 +02:00
Yaakov Selkowitz
109d2c0507 Fix build with OPENSSL_NO_ENGINE
Most ENGINE-related includes and code are already so guarded, this fixes
the last unguarded cases, and allows bind to build without
openssl-devel-engine (e.g. in RHEL 10 and ELN).
2024-09-12 15:20:58 -04:00
Petr Menšík
b57d168db4 Drop PostgreSQL dependencies not used by DLZ anymore 2024-07-31 22:45:53 +02:00
Petr Menšík
9149d53bb7 Make documentation links relative
Do not require absolute paths, but use relative links even to
non-existent buildroot subdirectory. It should compute it correctly.
2024-07-31 20:37:16 +02:00
Petr Menšík
401d76d40d Add optional QUERYTRACE feature 2024-07-31 20:37:16 +02:00
Petr Menšík
bdfa7b950b Remove separate license subpackage
Move license to libs subpackage, it is required for everything except
documentation. Include license file in docs and bind-libs, avoid extra
package just for single file.
2024-07-31 20:37:16 +02:00
Petr Menšík
2616681202 Conflict explicitly with more bind subpackages
List conflicts for each package explicitly, if it should conflict.
2024-07-31 20:37:16 +02:00
Petr Menšík
8346c4c3cd Warn both in README and spec rebase is not simple
bind-libs are needed by bind-dyndb-ldap, but symbol exported by
bind-libs changes with every rebase. Make it more obvious to external
people.
2024-07-31 18:44:25 +02:00
Jonathan Wright
41715d279a update to 9.18.28 rhbz#2299467
Fixes CVE-2024-4076
Fixes CVE-2024-1975
Fixes CVE-2024-1737
Fixes CVE-2024-0760
2024-07-26 11:11:36 -05:00
Fedora Release Engineering
245d49eb17 Rebuilt for https://fedoraproject.org/wiki/Fedora_41_Mass_Rebuild 2024-07-17 18:17:04 +00:00
741c22ffcd Pull in openssl-devel-engine to fix build 2024-07-09 14:36:09 +02:00
3a874644a6 Rebuilt for the bin-sbin merge
https://fedoraproject.org/wiki/Changes/Unify_bin_and_sbin
2024-07-09 12:51:00 +02:00
Petr Menšík
283878c63d Update to 9.18.26 (rhbz#2270476)
New Features

- The statistics channel now includes counters that indicate the number of currently connected TCP IPv4/IPv6 clients. [GL #4425]
- Added RESOLVER.ARPA to the built in empty zones. [GL #4580]

And bug fixes

https://downloads.isc.org/isc/bind9/9.18.26/doc/arm/html/notes.html#notes-for-bind-9-18-26
2024-04-22 12:13:52 +02:00
91d6033500 Fix build and installation when %_bindir==%_sbindir
Preparation for https://fedoraproject.org/wiki/Changes/Unify_bin_and_sbin.
No change in builds with unmerged sbin.
2024-04-14 17:20:10 +02:00
Petr Menšík
e79cdb27f2 Include dependency on python3-dns from SYSTEMTEST suite 2024-02-19 22:26:20 +01:00
Petr Menšík
fcaee16034 Update to BIND 9.18.24
https://downloads.isc.org/isc/bind9/9.18.24/doc/arm/html/notes.html#notes-for-bind-9-18-24

Resolves: CVE-2023-4408 CVE-2023-5517 CVE-2023-5679 CVE-2023-6516 CVE-2023-50387 CVE-2023-50868
2024-02-15 20:58:34 +01:00
Fedora Release Engineering
59820e13a5 Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild 2024-01-23 00:22:04 +00:00
Fedora Release Engineering
5e93e0b078 Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild 2024-01-19 14:23:22 +00:00
47 changed files with 12427 additions and 3371 deletions

18
.gitignore vendored
View file

@ -220,3 +220,21 @@ bind-9.7.2b1.tar.gz
/bind-9.18.20.tar.xz.asc /bind-9.18.20.tar.xz.asc
/bind-9.18.21.tar.xz /bind-9.18.21.tar.xz
/bind-9.18.21.tar.xz.asc /bind-9.18.21.tar.xz.asc
/bind-9.18.24.tar.xz
/bind-9.18.24.tar.xz.asc
/bind-9.18.26.tar.xz
/bind-9.18.26.tar.xz.asc
/bind-9.18.28.tar.xz
/bind-9.18.28.tar.xz.asc
/bind-9.18.29.tar.xz
/bind-9.18.29.tar.xz.asc
/bind-9.18.30.tar.xz
/bind-9.18.30.tar.xz.asc
/bind-9.18.31.tar.xz
/bind-9.18.31.tar.xz.asc
/bind-9.18.32.tar.xz
/bind-9.18.32.tar.xz.asc
/bind-9.18.33.tar.xz
/bind-9.18.33.tar.xz.asc
/bind-9.18.*.tar.xz
/bind-9.18.*.tar.xz.asc

View file

@ -0,0 +1,61 @@
From 31bd3a0996a85c0fced0c6ace3da1241b30dc397 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
Date: Thu, 30 Apr 2026 17:28:40 +0200
Subject: [PATCH] Use variable PROGRAM_SUFFIX in install target
--program-suffix is handled by configure itself. But some makefile rules
need to know it by a special value. Provide that to make multiple
versions installable together on a single system.
---
Makefile.top | 1 +
bin/check/Makefile.am | 6 +++---
bin/confgen/Makefile.am | 6 +++---
3 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/Makefile.top b/Makefile.top
index e186d15..91c076b 100644
--- a/Makefile.top
+++ b/Makefile.top
@@ -14,6 +14,7 @@ AM_CPPFLAGS = \
AM_LDFLAGS = \
$(STD_LDFLAGS)
LDADD =
+PROGRAM_SUFFIX =
if HOST_MACOS
AM_LDFLAGS += \
diff --git a/bin/check/Makefile.am b/bin/check/Makefile.am
index 8f63c35..36f232c 100644
--- a/bin/check/Makefile.am
+++ b/bin/check/Makefile.am
@@ -27,8 +27,8 @@ LDADD += \
bin_PROGRAMS = named-checkconf named-checkzone
install-exec-hook:
- ln -f $(DESTDIR)$(bindir)/named-checkzone \
- $(DESTDIR)$(bindir)/named-compilezone
+ ln -f $(DESTDIR)$(bindir)/named-checkzone$(PROGRAM_SUFFIX) \
+ $(DESTDIR)$(bindir)/named-compilezone$(PROGRAM_SUFFIX)
uninstall-hook:
- -rm -f $(DESTDIR)$(bindir)/named-compilezone
+ -rm -f $(DESTDIR)$(bindir)/named-compilezone$(PROGRAM_SUFFIX)
diff --git a/bin/confgen/Makefile.am b/bin/confgen/Makefile.am
index c1dca43..fe86dd7 100644
--- a/bin/confgen/Makefile.am
+++ b/bin/confgen/Makefile.am
@@ -23,8 +23,8 @@ libconfgen_la_SOURCES = \
sbin_PROGRAMS = tsig-keygen rndc-confgen
install-exec-hook:
- ln -f $(DESTDIR)$(sbindir)/tsig-keygen \
- $(DESTDIR)$(sbindir)/ddns-confgen
+ ln -f $(DESTDIR)$(sbindir)/tsig-keygen$(PROGRAM_SUFFIX) \
+ $(DESTDIR)$(sbindir)/ddns-confgen$(PROGRAM_SUFFIX)
uninstall-hook:
- -rm -f $(DESTDIR)$(sbindir)/ddns-confgen
+ -rm -f $(DESTDIR)$(sbindir)/ddns-confgen$(PROGRAM_SUFFIX)
--
2.54.0

View file

@ -14,6 +14,13 @@ More details about upstream project can be found on their
only upstream sources and packaging instructions for only upstream sources and packaging instructions for
[Fedora Project](https://fedoraproject.org). [Fedora Project](https://fedoraproject.org).
Any rebase requires to be built together with
[bind-dyndb-ldap](https://src.fedoraproject.org/rpms/bind-dyndb-ldap/) to prevent conflict
at installation of [freeipa-server-dns](https://src.fedoraproject.org/rpms/freeipa).
Stable bodhi updates are checked, but rawhide are not checked explicitly.
Symbol of libraries in *bind-libs* changes with every minor version change of bind,
therefore they break any package dependent on bind-libs.
## Subpackages ## Subpackages
The package contains several subpackages, some of them can be disabled on rebuild. The package contains several subpackages, some of them can be disabled on rebuild.
@ -21,7 +28,6 @@ The package contains several subpackages, some of them can be disabled on rebuil
* **bind** -- *named* daemon providing DNS server * **bind** -- *named* daemon providing DNS server
* **bind-utils** -- set of tools to analyse DNS responses or update entries (dig, host) * **bind-utils** -- set of tools to analyse DNS responses or update entries (dig, host)
* **bind-doc** -- documentation for current bind, *BIND 9 Administrator Reference Manual*. * **bind-doc** -- documentation for current bind, *BIND 9 Administrator Reference Manual*.
* **bind-license** -- Shared license for all packages but bind-export-libs.
* **bind-libs** -- Shared libraries used by some others programs * **bind-libs** -- Shared libraries used by some others programs
* **bind-devel** -- Development headers for libs. Can be disabled by `--without DEVEL` * **bind-devel** -- Development headers for libs. Can be disabled by `--without DEVEL`
@ -30,4 +36,3 @@ The package contains several subpackages, some of them can be disabled on rebuil
* *GSSTSIG* -- Support for Kerberos authentication in BIND. * *GSSTSIG* -- Support for Kerberos authentication in BIND.
* *LMDB* -- Support for dynamic database for managing runtime added zones. Provides faster removal of added zone with much less overhead. But requires lmdb linked to base libs. * *LMDB* -- Support for dynamic database for managing runtime added zones. Provides faster removal of added zone with much less overhead. But requires lmdb linked to base libs.
* *DLZ* -- Support for dynamic loaded modules providing support for features *bind-sdb* provides, but only small module is required.

View file

@ -0,0 +1,854 @@
From 2a4786b0edde59274f682c9bd4ae4026c2d08218 Mon Sep 17 00:00:00 2001
From: Evan Hunt <each@isc.org>
Date: Wed, 1 Jul 2026 22:56:02 -0700
Subject: [PATCH] add isctest.mark method for ecdsa_deterinistic
This checks support for ECDSA deterministic mode in the cryptography
library.
(cherry picked from commit 6e44151466864d3dd783a20da83d01028781d3e2)
(cherry picked from commit dc3f52388427f4f94087d984d5a2088b925810e2)
Reproducer for #5874 NSEC3 impersonation
LLM generated.
(cherry picked from commit f3e2eb333be3ac636f745aa13cfb8d9ee8af87d8)
(cherry picked from commit 35e3d49d2222c13786a06021c7ed583d2a656e51)
Update reproducer #5874
Update the llm generated reproducer:
- Move server.py into ans1/ans.py
- Remove unnecessary named.conf configuration options
- Add comments describing the steps (copied from GL issue)
- Rename system test
(cherry picked from commit c1321fef165a2ef8c2bff971901c58941e8e694c)
(cherry picked from commit 833dd3b230b92596074e8da15b12298f46c939f2)
---
bin/tests/system/chain/ans3/ans.py | 10 +-
bin/tests/system/chain/ans4/ans.py | 8 +-
bin/tests/system/cookie/ans9/ans.py | 7 +-
bin/tests/system/digdelv/ans8/ans.py | 7 +-
bin/tests/system/dnssec/ans10/ans.py | 11 +-
bin/tests/system/forward/ans11/ans.py | 12 +-
bin/tests/system/isctest/mark.py | 12 +
.../system/nsec3_impersonation/ans1/ans.py | 280 ++++++++++++++++++
.../nsec3_impersonation/ns2/named.conf.j2 | 33 +++
.../tests_nsec3_impersonation.py | 152 ++++++++++
bin/tests/system/qmin/ans2/ans.py | 11 +-
bin/tests/system/qmin/ans3/ans.py | 11 +-
bin/tests/system/qmin/ans4/ans.py | 11 +-
bin/tests/system/resolver/ans10/ans.py | 12 +-
14 files changed, 529 insertions(+), 48 deletions(-)
create mode 100644 bin/tests/system/nsec3_impersonation/ans1/ans.py
create mode 100644 bin/tests/system/nsec3_impersonation/ns2/named.conf.j2
create mode 100644 bin/tests/system/nsec3_impersonation/tests_nsec3_impersonation.py
diff --git a/bin/tests/system/chain/ans3/ans.py b/bin/tests/system/chain/ans3/ans.py
index 0a031c1145..7c54c3c51a 100644
--- a/bin/tests/system/chain/ans3/ans.py
+++ b/bin/tests/system/chain/ans3/ans.py
@@ -19,10 +19,10 @@ import sys
import signal
import socket
import select
-from datetime import datetime, timedelta
-import functools
-import dns, dns.message, dns.query
+import dns
+import dns.message
+import dns.query
from dns.rdatatype import *
from dns.rdataclass import *
from dns.rcode import *
@@ -173,9 +173,9 @@ else:
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
- except select.error as e:
+ except select.error:
break
- except socket.error as e:
+ except socket.error:
break
except KeyboardInterrupt:
break
diff --git a/bin/tests/system/chain/ans4/ans.py b/bin/tests/system/chain/ans4/ans.py
index c969117368..de536b25bd 100755
--- a/bin/tests/system/chain/ans4/ans.py
+++ b/bin/tests/system/chain/ans4/ans.py
@@ -22,7 +22,9 @@ import select
from datetime import datetime, timedelta
import functools
-import dns, dns.message, dns.query
+import dns
+import dns.message
+import dns.query
from dns.rdatatype import *
from dns.rdataclass import *
from dns.rcode import *
@@ -371,9 +373,9 @@ else:
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
- except select.error as e:
+ except select.error:
break
- except socket.error as e:
+ except socket.error:
break
except KeyboardInterrupt:
break
diff --git a/bin/tests/system/cookie/ans9/ans.py b/bin/tests/system/cookie/ans9/ans.py
index 3b0f82cc1d..2710386d74 100644
--- a/bin/tests/system/cookie/ans9/ans.py
+++ b/bin/tests/system/cookie/ans9/ans.py
@@ -15,9 +15,6 @@ import sys
import signal
import socket
import select
-from datetime import datetime, timedelta
-import time
-import functools
import dns
import dns.edns
@@ -257,9 +254,9 @@ else:
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
- except select.error as e:
+ except select.error:
break
- except socket.error as e:
+ except socket.error:
break
except KeyboardInterrupt:
break
diff --git a/bin/tests/system/digdelv/ans8/ans.py b/bin/tests/system/digdelv/ans8/ans.py
index 3e18edc1cc..1896a2bafc 100644
--- a/bin/tests/system/digdelv/ans8/ans.py
+++ b/bin/tests/system/digdelv/ans8/ans.py
@@ -17,7 +17,8 @@ import socket
import select
import struct
-import dns, dns.message
+import dns
+import dns.message
from dns.rcode import *
modes = [
@@ -109,9 +110,9 @@ hung_conns = []
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
- except select.error as e:
+ except select.error:
break
- except socket.error as e:
+ except socket.error:
break
except KeyboardInterrupt:
break
diff --git a/bin/tests/system/dnssec/ans10/ans.py b/bin/tests/system/dnssec/ans10/ans.py
index 84bf0a2642..46af72a147 100644
--- a/bin/tests/system/dnssec/ans10/ans.py
+++ b/bin/tests/system/dnssec/ans10/ans.py
@@ -16,10 +16,11 @@ import signal
import socket
import select
from datetime import datetime, timedelta
-import time
-import functools
-import dns, dns.message, dns.query, dns.flags
+import dns
+import dns.message
+import dns.query
+import dns.flags
from dns.rdatatype import *
from dns.rdataclass import *
from dns.rcode import *
@@ -140,9 +141,9 @@ else:
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
- except select.error as e:
+ except select.error:
break
- except socket.error as e:
+ except socket.error:
break
except KeyboardInterrupt:
break
diff --git a/bin/tests/system/forward/ans11/ans.py b/bin/tests/system/forward/ans11/ans.py
index 00b5895f76..d5b8a5b037 100644
--- a/bin/tests/system/forward/ans11/ans.py
+++ b/bin/tests/system/forward/ans11/ans.py
@@ -16,11 +16,11 @@ import signal
import socket
import select
import struct
-from datetime import datetime, timedelta
-import time
-import functools
-import dns, dns.message, dns.query, dns.flags
+import dns
+import dns.message
+import dns.query
+import dns.flags
from dns.rdatatype import *
from dns.rdataclass import *
from dns.rcode import *
@@ -192,9 +192,9 @@ hung_conns = []
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
- except select.error as e:
+ except select.error:
break
- except socket.error as e:
+ except socket.error:
break
except KeyboardInterrupt:
break
diff --git a/bin/tests/system/isctest/mark.py b/bin/tests/system/isctest/mark.py
index 53860a806c..098af5acfc 100644
--- a/bin/tests/system/isctest/mark.py
+++ b/bin/tests/system/isctest/mark.py
@@ -88,3 +88,15 @@ softhsm2_environment = pytest.mark.skipif(
),
reason="SOFTHSM2_CONF and SOFTHSM2_MODULE environmental variables must be set and pkcs11-tool and softhsm2-util tools present",
)
+
+ecdsa_deterministic = False
+try:
+ from cryptography.hazmat.backends import default_backend
+
+ ecdsa_deterministic = default_backend().ecdsa_deterministic_supported()
+except Exception: # pylint: disable=broad-except
+ pass
+
+with_ecdsa_deterministic = pytest.mark.skipif(
+ not ecdsa_deterministic, reason="ECDSA deterministic signing is not supported"
+)
diff --git a/bin/tests/system/nsec3_impersonation/ans1/ans.py b/bin/tests/system/nsec3_impersonation/ans1/ans.py
new file mode 100644
index 0000000000..177e79c195
--- /dev/null
+++ b/bin/tests/system/nsec3_impersonation/ans1/ans.py
@@ -0,0 +1,280 @@
+#!/usr/bin/python3
+
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+from collections.abc import AsyncGenerator
+from dataclasses import dataclass
+from pathlib import Path
+
+import json
+
+from cryptography.hazmat.primitives import serialization
+
+import dns.dnssec
+import dns.flags
+import dns.message
+import dns.name
+import dns.rcode
+import dns.rdata
+import dns.rdataclass
+import dns.rdatatype
+import dns.rrset
+
+from isctest.asyncserver import (
+ AsyncDnsServer,
+ DnsResponseSend,
+ QueryContext,
+ ResponseHandler,
+)
+
+TTL = 300
+TLD = "tld.test."
+APEX_HASH = "1B40241KFORIOG780N4IKSCRLVETPCTQ"
+ATTACKER = f"{APEX_HASH.lower()}.{TLD}"
+VICTIM = f"victim.{TLD}"
+AUTH_IP = "10.53.0.1"
+
+
+@dataclass(frozen=True)
+class Key:
+ zone: dns.name.Name
+ private_key: object
+ dnskey: dns.rdata.Rdata
+ ds: dns.rdata.Rdata
+
+
+def name(text: str) -> dns.name.Name:
+ return dns.name.from_text(text)
+
+
+def load_keys() -> dict[str, Key]:
+ path = Path(__file__).resolve().parent / "keys.json"
+ with path.open(encoding="utf-8") as keys_file:
+ raw_keys = json.load(keys_file)
+
+ keys = {}
+ for zone, raw_key in raw_keys.items():
+ private_key = serialization.load_pem_private_key(
+ raw_key["private_pem"].encode("ascii"),
+ password=None,
+ )
+ dnskey = dns.rdata.from_text(
+ dns.rdataclass.IN, dns.rdatatype.DNSKEY, raw_key["dnskey"]
+ )
+ ds = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.DS, raw_key["ds"])
+ keys[zone] = Key(name(zone), private_key, dnskey, ds)
+
+ return keys
+
+
+def rrset(owner: str, rdtype: dns.rdatatype.RdataType, *rdatas: str) -> dns.rrset.RRset:
+ return dns.rrset.from_text(owner, TTL, dns.rdataclass.IN, rdtype, *rdatas)
+
+
+def rrset_from_rdata(owner: str, rdata: dns.rdata.Rdata) -> dns.rrset.RRset:
+ return dns.rrset.from_rdata(name(owner), TTL, rdata)
+
+
+def rrsig_rrset(covered: dns.rrset.RRset, signer: Key) -> dns.rrset.RRset:
+ rrsig = dns.dnssec.sign(
+ covered,
+ signer.private_key,
+ signer.zone,
+ signer.dnskey,
+ lifetime=86400,
+ verify=True,
+ )
+ return dns.rrset.from_rdata(covered.name, covered.ttl, rrsig)
+
+
+def add_signed(
+ section: list[dns.rrset.RRset], covered: dns.rrset.RRset, signer: Key
+) -> None:
+ section.append(covered)
+ section.append(rrsig_rrset(covered, signer))
+
+
+def dnskey_rrset(zone: str, zone_key: Key) -> dns.rrset.RRset:
+ return rrset_from_rdata(zone, zone_key.dnskey)
+
+
+def ds_rrset(zone: str, zone_key: Key) -> dns.rrset.RRset:
+ return rrset_from_rdata(zone, zone_key.ds)
+
+
+def soa_rrset(zone: str) -> dns.rrset.RRset:
+ return rrset(
+ zone,
+ dns.rdatatype.SOA,
+ f"ns.{zone} hostmaster.{zone} 1 3600 600 86400 300",
+ )
+
+
+def ns_rrset(zone: str, ns_target: str) -> dns.rrset.RRset:
+ return rrset(zone, dns.rdatatype.NS, ns_target)
+
+
+def glue_rrset(ns_target: str, address: str) -> dns.rrset.RRset:
+ return rrset(ns_target, dns.rdatatype.A, address)
+
+
+def answer_dnskey(response: dns.message.Message, zone: str, zone_key: Key) -> None:
+ add_signed(response.answer, dnskey_rrset(zone, zone_key), zone_key)
+
+
+def answer_soa(response: dns.message.Message, zone: str, zone_key: Key) -> None:
+ add_signed(response.answer, soa_rrset(zone), zone_key)
+
+
+def answer_ns(
+ response: dns.message.Message, zone: str, ns_target: str, zone_key: Key
+) -> None:
+ add_signed(response.answer, ns_rrset(zone, ns_target), zone_key)
+
+
+class SignedResponseHandler(ResponseHandler):
+ def __init__(self, keys: dict[str, Key]) -> None:
+ self.keys = keys
+
+ async def get_responses(
+ self, qctx: QueryContext
+ ) -> AsyncGenerator[DnsResponseSend, None]:
+ qctx.prepare_new_response(with_zone_data=False)
+ qctx.response.flags |= dns.flags.AA
+ qctx.response.set_rcode(dns.rcode.NOERROR)
+ self.respond(qctx)
+ yield DnsResponseSend(qctx.response, authoritative=True)
+
+ def respond(self, qctx: QueryContext) -> None:
+ raise NotImplementedError
+
+
+def child_nsec3_rrset() -> dns.rrset.RRset:
+ rdata = dns.rdata.from_text(
+ dns.rdataclass.IN,
+ dns.rdatatype.NSEC3,
+ f"1 0 0 - {APEX_HASH} NS SOA RRSIG DNSKEY NSEC3PARAM",
+ )
+ return dns.rrset.from_rdata(name(f"{APEX_HASH}.{TLD}"), TTL, rdata)
+
+
+def forged_nxdomain(response: dns.message.Message, keys: dict[str, Key]) -> None:
+ response.set_rcode(dns.rcode.NXDOMAIN)
+
+ add_signed(response.authority, soa_rrset(TLD), keys[TLD])
+
+ # The owner name derives zone "tld.test.", but the RRSIG signer is the
+ # malicious child zone "1b40241kforiog780n4ikscrlvetpctq.tld.test.".
+ add_signed(response.authority, child_nsec3_rrset(), keys[ATTACKER])
+
+
+class VictimForgedNxdomainHandler(SignedResponseHandler):
+ """
+ This serves the forged response for the victim's domain.
+ """
+
+ def match(self, qctx: QueryContext) -> bool:
+ return qctx.qname == name(VICTIM) and qctx.qtype == dns.rdatatype.A
+
+ def respond(self, qctx: QueryContext) -> None:
+ forged_nxdomain(qctx.response, self.keys)
+
+
+class ChildDsHandler(SignedResponseHandler):
+ """
+ This will spoof the response for the malicious zone when qtype is DS.
+ It is actually a validly signed DS response.
+ """
+
+ def match(self, qctx: QueryContext) -> bool:
+ return qctx.qname == name(ATTACKER) and qctx.qtype == dns.rdatatype.DS
+
+ def respond(self, qctx: QueryContext) -> None:
+ response = qctx.response
+ zone = ATTACKER
+ child_key = self.keys[ATTACKER]
+ parent_key = self.keys[TLD]
+
+ add_signed(response.answer, ds_rrset(zone, child_key), parent_key)
+
+
+class AttackerZoneHandler(SignedResponseHandler):
+ """
+ Acts as the malicious authoritative name server. The zone being served
+ is the hashed label of the parent zone (tld.test). This will respond
+ for all queries qtype SOA, DNSKEY, NS at the apex. Any names below
+ the apex are answered with an NXDOMAIN with no NSEC or NSEC3 present.
+ """
+
+ def match(self, qctx: QueryContext) -> bool:
+ return qctx.qname.is_subdomain(name(ATTACKER))
+
+ def respond(self, qctx: QueryContext) -> None:
+ if qctx.qname == name(ATTACKER):
+ if qctx.qtype == dns.rdatatype.DNSKEY:
+ answer_dnskey(qctx.response, ATTACKER, self.keys[ATTACKER])
+ elif qctx.qtype == dns.rdatatype.SOA:
+ answer_soa(qctx.response, ATTACKER, self.keys[ATTACKER])
+ else:
+ answer_ns(
+ qctx.response, ATTACKER, f"ns.{ATTACKER}", self.keys[ATTACKER]
+ )
+ qctx.response.additional.append(glue_rrset(f"ns.{ATTACKER}", AUTH_IP))
+ return
+
+ qctx.response.set_rcode(dns.rcode.NXDOMAIN)
+ add_signed(qctx.response.authority, soa_rrset(ATTACKER), self.keys[ATTACKER])
+
+
+class TldZoneHandler(SignedResponseHandler):
+ """
+ Acts as the TLD who is being used in the attack, but is not a standard
+ name server. It only responds with validly signed records for DNSKEY, SOA
+ and NS on the apex. Any names below the apex are answered with an NXDOMAIN
+ with no NSEC or NSEC3 present.
+
+ If we turn this into a regular name server than the attack won't work.
+ The attack assumes that the adversary can inject these responses on-path.
+ """
+
+ def match(self, qctx: QueryContext) -> bool:
+ return qctx.qname.is_subdomain(name(TLD))
+
+ def respond(self, qctx: QueryContext) -> None:
+ if qctx.qname == name(TLD):
+ if qctx.qtype == dns.rdatatype.DNSKEY:
+ answer_dnskey(qctx.response, TLD, self.keys[TLD])
+ elif qctx.qtype == dns.rdatatype.SOA:
+ answer_soa(qctx.response, TLD, self.keys[TLD])
+ else:
+ answer_ns(qctx.response, TLD, "ns.tld.test.", self.keys[TLD])
+ qctx.response.additional.append(glue_rrset("ns.tld.test.", AUTH_IP))
+ return
+
+ qctx.response.set_rcode(dns.rcode.NXDOMAIN)
+ add_signed(qctx.response.authority, soa_rrset(TLD), self.keys[TLD])
+
+
+def main() -> None:
+ keys = load_keys()
+ server = AsyncDnsServer(default_aa=True)
+ server.install_response_handlers(
+ VictimForgedNxdomainHandler(keys),
+ ChildDsHandler(keys),
+ AttackerZoneHandler(keys),
+ TldZoneHandler(keys),
+ )
+ server.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/bin/tests/system/nsec3_impersonation/ns2/named.conf.j2 b/bin/tests/system/nsec3_impersonation/ns2/named.conf.j2
new file mode 100644
index 0000000000..2c9b0bba9e
--- /dev/null
+++ b/bin/tests/system/nsec3_impersonation/ns2/named.conf.j2
@@ -0,0 +1,33 @@
+// validating resolver
+
+options {
+ query-source address 10.53.0.2;
+ notify-source 10.53.0.2;
+ transfer-source 10.53.0.2;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.2; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation yes;
+};
+
+controls {
+ inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+include "../../_common/rndc.key";
+
+zone "." {
+ type hint;
+ file "../../_common/root.hint";
+};
+
+zone "tld.test" {
+ type static-stub;
+ server-addresses { 10.53.0.1; };
+};
+
+trust-anchors {
+ tld.test. static-key 257 3 13 "@TLD_DNSKEY@";
+};
diff --git a/bin/tests/system/nsec3_impersonation/tests_nsec3_impersonation.py b/bin/tests/system/nsec3_impersonation/tests_nsec3_impersonation.py
new file mode 100644
index 0000000000..bd9bd275b6
--- /dev/null
+++ b/bin/tests/system/nsec3_impersonation/tests_nsec3_impersonation.py
@@ -0,0 +1,152 @@
+#!/usr/bin/python3
+
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+from pathlib import Path
+
+import json
+
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import ec
+
+import dns.dnssec
+import dns.flags
+import dns.name
+import dns.rdataclass
+import dns.rdatatype
+import pytest
+
+import isctest
+import isctest.mark
+
+APEX_HASH = "1B40241KFORIOG780N4IKSCRLVETPCTQ"
+ATTACKER = f"{APEX_HASH.lower()}.tld.test."
+VICTIM = "victim.tld.test."
+AUTH = "10.53.0.1"
+RESOLVER = "10.53.0.2"
+
+pytestmark = [
+ isctest.mark.with_ecdsa_deterministic,
+ pytest.mark.extra_artifacts(
+ [
+ "ans*/ans.run",
+ "ans*/keys.json",
+ ]
+ ),
+]
+
+
+def _make_key(zone):
+ private_key = ec.generate_private_key(ec.SECP256R1())
+ dnskey = dns.dnssec.make_dnskey(
+ private_key.public_key(),
+ algorithm="ECDSAP256SHA256",
+ flags=257,
+ )
+ ds = dns.dnssec.make_ds(dns.name.from_text(zone), dnskey, "SHA256")
+ private_pem = private_key.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=serialization.NoEncryption(),
+ ).decode("ascii")
+ return {
+ "private_pem": private_pem,
+ "dnskey": dnskey.to_text(),
+ "ds": ds.to_text(),
+ }
+
+
+def bootstrap():
+ zones = ["tld.test.", ATTACKER]
+ keys = {zone: _make_key(zone) for zone in zones}
+
+ Path("ans1/keys.json").write_text(json.dumps(keys, indent=2), encoding="ascii")
+
+ tld_dnskey = "".join(keys["tld.test."]["dnskey"].split()[3:])
+ return {"TLD_DNSKEY": tld_dnskey}
+
+
+def check_dnskey_response(zone):
+ query = isctest.query.create(zone, "DNSKEY")
+ response = isctest.query.tcp(query, AUTH)
+
+ isctest.check.noerror(response)
+ assert response.flags & dns.flags.AA
+ assert (
+ response.get_rrset(
+ response.answer,
+ dns.name.from_text(zone),
+ dns.rdataclass.IN,
+ dns.rdatatype.DNSKEY,
+ )
+ is not None
+ ), response
+
+
+def check_ds_response(zone):
+ query = isctest.query.create(zone, "DS")
+ response = isctest.query.tcp(query, AUTH)
+
+ isctest.check.noerror(response)
+ assert response.flags & dns.flags.AA
+ assert (
+ response.get_rrset(
+ response.answer,
+ dns.name.from_text(zone),
+ dns.rdataclass.IN,
+ dns.rdatatype.DS,
+ )
+ is not None
+ ), response
+
+
+def test_attack_responses():
+ check_dnskey_response("tld.test.")
+ check_dnskey_response(ATTACKER)
+ check_ds_response(ATTACKER)
+
+ query = isctest.query.create(VICTIM, "A")
+ response = isctest.query.tcp(query, AUTH)
+
+ isctest.check.nxdomain(response)
+ assert response.flags & dns.flags.AA
+
+ nsec3_owner = dns.name.from_text(f"{APEX_HASH}.tld.test.")
+ nsec3 = response.get_rrset(
+ response.authority,
+ nsec3_owner,
+ dns.rdataclass.IN,
+ dns.rdatatype.NSEC3,
+ )
+ rrsig = response.get_rrset(
+ response.authority,
+ nsec3_owner,
+ dns.rdataclass.IN,
+ dns.rdatatype.RRSIG,
+ covers=dns.rdatatype.NSEC3,
+ )
+
+ assert nsec3 is not None, response
+ assert rrsig is not None, response
+ assert rrsig[0].signer == dns.name.from_text(ATTACKER)
+
+
+def test_nsec3_impersonation():
+ """
+ Reproducer for #5874:
+ F-006 DNSSEC Validation Bypass NSEC3 Apex Hash Label Parent Impersonation
+ """
+ query = isctest.query.create(VICTIM, "A")
+ response = isctest.query.tcp(query, RESOLVER)
+
+ isctest.check.noadflag(response)
+ isctest.check.servfail(response)
diff --git a/bin/tests/system/qmin/ans2/ans.py b/bin/tests/system/qmin/ans2/ans.py
index d372c2003b..9343fbc8ef 100755
--- a/bin/tests/system/qmin/ans2/ans.py
+++ b/bin/tests/system/qmin/ans2/ans.py
@@ -15,11 +15,12 @@ import sys
import signal
import socket
import select
-from datetime import datetime, timedelta
import time
-import functools
-import dns, dns.message, dns.query, dns.flags
+import dns
+import dns.message
+import dns.query
+import dns.flags
from dns.rdatatype import *
from dns.rdataclass import *
from dns.rcode import *
@@ -432,9 +433,9 @@ else:
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
- except select.error as e:
+ except select.error:
break
- except socket.error as e:
+ except socket.error:
break
except KeyboardInterrupt:
break
diff --git a/bin/tests/system/qmin/ans3/ans.py b/bin/tests/system/qmin/ans3/ans.py
index b5ae73c3fa..4e7250790f 100755
--- a/bin/tests/system/qmin/ans3/ans.py
+++ b/bin/tests/system/qmin/ans3/ans.py
@@ -15,11 +15,12 @@ import sys
import signal
import socket
import select
-from datetime import datetime, timedelta
import time
-import functools
-import dns, dns.message, dns.query, dns.flags
+import dns
+import dns.message
+import dns.query
+import dns.flags
from dns.rdatatype import *
from dns.rdataclass import *
from dns.rcode import *
@@ -261,9 +262,9 @@ else:
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
- except select.error as e:
+ except select.error:
break
- except socket.error as e:
+ except socket.error:
break
except KeyboardInterrupt:
break
diff --git a/bin/tests/system/qmin/ans4/ans.py b/bin/tests/system/qmin/ans4/ans.py
index 517217aec1..2d5556daff 100755
--- a/bin/tests/system/qmin/ans4/ans.py
+++ b/bin/tests/system/qmin/ans4/ans.py
@@ -15,11 +15,12 @@ import sys
import signal
import socket
import select
-from datetime import datetime, timedelta
import time
-import functools
-import dns, dns.message, dns.query, dns.flags
+import dns
+import dns.message
+import dns.query
+import dns.flags
from dns.rdatatype import *
from dns.rdataclass import *
from dns.rcode import *
@@ -320,9 +321,9 @@ else:
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
- except select.error as e:
+ except select.error:
break
- except socket.error as e:
+ except socket.error:
break
except KeyboardInterrupt:
break
diff --git a/bin/tests/system/resolver/ans10/ans.py b/bin/tests/system/resolver/ans10/ans.py
index 6e95dbbfc6..d637c63e5a 100644
--- a/bin/tests/system/resolver/ans10/ans.py
+++ b/bin/tests/system/resolver/ans10/ans.py
@@ -15,11 +15,11 @@ import sys
import signal
import socket
import select
-from datetime import datetime, timedelta
-import time
-import functools
-import dns, dns.message, dns.query, dns.flags
+import dns
+import dns.message
+import dns.query
+import dns.flags
from dns.rdatatype import *
from dns.rdataclass import *
from dns.rcode import *
@@ -128,9 +128,9 @@ else:
while running:
try:
inputready, outputready, exceptready = select.select(input, [], [])
- except select.error as e:
+ except select.error:
break
- except socket.error as e:
+ except socket.error:
break
except KeyboardInterrupt:
break
--
2.55.0

View file

@ -0,0 +1,67 @@
From 608026780a43abe5b23a9af3af21808369032158 Mon Sep 17 00:00:00 2001
From: Evan Hunt <each@isc.org>
Date: Thu, 21 May 2026 14:41:55 -0700
Subject: [PATCH] Check NSEC3 signer matches the owning zone
When validating NSEC3 records, reject any signature whose signer field
does not match the zone owning the NSEC3.
This ensures that a child zone cannot impersonate its parent and forge
NXDOMAIN responses for sibling domains.
Fixes: isc-projects/bind9#5874
(cherry picked from commit 6e5066bb1f0f12d090e8707adb7d6ccf74f8012b)
(cherry picked from commit c9cb6a5e24e43489cf3fd4d4cc2193b6a74499cb)
---
lib/dns/dnssec.c | 19 +++++++++++++++++--
lib/isc/result.c | 2 +-
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index b12529b5d5..9b9b1f2bb2 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -424,10 +424,25 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
}
/*
- * NS, SOA and DNSKEY records are signed by their owner.
- * DS records are signed by the parent.
+ * NS, SOA and DNSKEY records are signed by their owners.
+ * NSEC3 records are signed by the apex, exactly one level up
+ * from their owner names.
+ * DS records are signed by the parent zone.
*/
switch (set->type) {
+ case dns_rdatatype_nsec3: {
+ dns_name_t apex = DNS_NAME_INITEMPTY;
+ labels = dns_name_countlabels(name);
+ if (labels <= 1) {
+ inc_stat(dns_dnssecstats_fail);
+ return DNS_R_INVALIDNSEC3;
+ }
+ dns_name_split(name, labels - 1, NULL, &apex);
+ if (!dns_name_equal(&apex, &sig.signer)) {
+ inc_stat(dns_dnssecstats_fail);
+ return DNS_R_SIGINVALID;
+ }
+ } break;
case dns_rdatatype_ns:
case dns_rdatatype_soa:
case dns_rdatatype_dnskey:
diff --git a/lib/isc/result.c b/lib/isc/result.c
index 83e8cfeed7..b76c3d1f7b 100644
--- a/lib/isc/result.c
+++ b/lib/isc/result.c
@@ -198,7 +198,7 @@ static const char *description[ISC_R_NRESULTS] = {
[DNS_R_COVERINGNSEC] = "covering NSEC record returned",
[DNS_R_MXISADDRESS] = "MX is an address",
[DNS_R_DUPLICATE] = "duplicate query",
- [DNS_R_INVALIDNSEC3] = "invalid NSEC3 owner name (wildcard)",
+ [DNS_R_INVALIDNSEC3] = "invalid NSEC3 owner name",
[DNS_R_NOTPRIMARY] = "not primary",
[DNS_R_BROKENCHAIN] = "broken trust chain",
[DNS_R_EXPIRED] = "expired",
--
2.55.0

View file

@ -0,0 +1,320 @@
From a4ce4c0ce5b8d7630417730dc1b98bf554e0801f Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 19 May 2026 10:44:04 +1000
Subject: [PATCH] Check that dns_name_fromwire honours the active region
When reading DNS records from the wire the active region of the
source buffer is set to the end of the current record. dns_name_fromwire
should fail if it attempts to read past this setting.
(cherry picked from commit 3ed821d68b15fe4e6288e3054397d6bce7e65968)
(cherry picked from commit d413c9ac2e29a728531354a69c8c8234c01b7d1e)
Check that a short PRIVATEDNS record is rejected
A bug in dns_name_fromwire meant that short PRIVATEDNS key
records where being accepted. Test that this is no longer
the case.
(cherry picked from commit f48d48027384d8c2210b5ce9e3eac7af101ead3d)
(cherry picked from commit 19ac8b8e46aeb0a15e217bc7bdf485b31b87d9b4)
POC for PRIVATEDNS DNSKEY overrun not being detected
Construct a DNS message where a PRIVATEDNS DNSKEY identifier
overruns the record boundary by 3 byte so that the label ends
at the end of the compression pointer for the next record. The
next type is less than 256 so the next octet is 00 terminating
the identifier name. The transfered zone is then written to
disk using master-format text triggering the assertion when the
truncated identier is discovered.
Note this test will produce a false result in versions of
BIND that do not check the PRIVATEDNS identifier as it looks
for the error message when the transfer is aborted.
(cherry picked from commit 9ce3bce8bc8b4e9c6a9b1e84b5849c33eb27830e)
(cherry picked from commit 8e066d3fc369e3346f22bb5cfb67a7ab08a74034)
---
bin/tests/system/xfer/ans9/ans.py | 142 ++++++++++++++++++++++++
bin/tests/system/xfer/ns6/named.conf.in | 9 ++
bin/tests/system/xfer/tests.sh | 16 +++
tests/dns/name_test.c | 30 +++++
tests/dns/rdata_test.c | 21 ++++
5 files changed, 218 insertions(+)
create mode 100644 bin/tests/system/xfer/ans9/ans.py
diff --git a/bin/tests/system/xfer/ans9/ans.py b/bin/tests/system/xfer/ans9/ans.py
new file mode 100644
index 0000000000..a9e73953ee
--- /dev/null
+++ b/bin/tests/system/xfer/ans9/ans.py
@@ -0,0 +1,142 @@
+"""
+Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+
+SPDX-License-Identifier: MPL-2.0
+
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, you can obtain one at https://mozilla.org/MPL/2.0/.
+
+See the COPYRIGHT file distributed with this work for additional
+information regarding copyright ownership.
+"""
+
+from collections.abc import AsyncGenerator
+
+import dns.name
+import dns.rcode
+import dns.rdatatype
+import dns.rrset
+
+from isctest.asyncserver import (
+ ControllableAsyncDnsServer,
+ DnsResponseSend,
+ DomainHandler,
+ QueryContext,
+ ResponseAction,
+ ToggleResponsesCommand,
+)
+
+
+class AXFRServer(DomainHandler):
+ """
+ Yield SOA and AXFR responses. Every new AXFR response increments the SOA
+ version.
+ """
+
+ domains = ["xfr-and-reconfig", "private-dns-overrun"]
+
+ def __init__(self) -> None:
+ super().__init__()
+ self.soa_version = 0
+
+ async def get_responses(
+ self, qctx: QueryContext
+ ) -> AsyncGenerator[ResponseAction, None]:
+ # This is oversimplified because I am lazy - we are appending the SOA
+ # RRset to the ANSWER section for _every_ QTYPE. named is only
+ # expected to send a SOA query over UDP and then an AXFR query over
+ # TCP. Responses to both of those start with a SOA RRset in the ANSWER
+ # section :-)
+ soa_message = qctx.response
+ soa_rrset = dns.rrset.from_text(
+ qctx.qname,
+ 300,
+ qctx.qclass,
+ dns.rdatatype.SOA,
+ f". . {self.soa_version} 0 0 0 0",
+ )
+ soa_message.answer.append(soa_rrset)
+
+ yield DnsResponseSend(soa_message)
+
+ if qctx.qtype == dns.rdatatype.SOA:
+ # If QTYPE=SOA, the SOA record is the complete response.
+ return
+
+ if qctx.qtype != dns.rdatatype.AXFR:
+ # If QTYPE=AXFR, we will continue cramming RRsets into the ANSWER
+ # section of a subsequent DNS message below.
+ #
+ # If QTYPE was not SOA or AXFR, abort. Yeah, we just sent a broken
+ # response by yielding DnsResponseSend() with a SOA RRset in the
+ # ANSWER section above. We will have to carry that burden for the
+ # rest of our lives.
+ return
+
+ # Send just the obligatory NS RRset at zone apex in the next message.
+ # This is stupidly inefficient, but makes looping below simpler as we
+ # will already have been done with the mandatory stuff by then.
+ ns_message = qctx.prepare_new_response()
+ ns_rrset = dns.rrset.from_text(
+ qctx.qname, 300, qctx.qclass, dns.rdatatype.NS, "."
+ )
+ ns_message.answer.append(ns_rrset)
+
+ yield DnsResponseSend(ns_message)
+
+ # Generate the AXFR with a txt rrset.
+ txt_message = qctx.prepare_new_response()
+ txt_rrset = dns.rrset.from_text(
+ qctx.qname,
+ 300,
+ qctx.qclass,
+ dns.rdatatype.TXT,
+ "foo bar",
+ )
+ txt_message.answer.append(txt_rrset)
+
+ yield DnsResponseSend(txt_message)
+
+ if qctx.qname == dns.name.from_text("private-dns-overrun"):
+ # A message where the malformed DNSKEY algorithm identifier
+ # finishes on a 00 byte in the next record. Assumes the
+ # next record starts with a compression pointer which is
+ # followed by the type which starts with 00.
+
+ # Generate malformed PRIVATE DNS DNSKEY
+ dnskey_message = qctx.prepare_new_response()
+ dnskey_rrset = dns.rrset.from_text(
+ qctx.qname,
+ 300,
+ qctx.qclass,
+ dns.rdatatype.DNSKEY,
+ "\\# 12 00 00 00 fd 09 00 00 00 00 00 00 00",
+ )
+ dnskey_message.answer.append(dnskey_rrset)
+ # Generate well formed PRIVATE DNS DNSKEY
+ dnskey_rrset = dns.rrset.from_text(
+ qctx.qname,
+ 300,
+ qctx.qclass,
+ dns.rdatatype.DNSKEY,
+ "\\# 12 00 00 00 fd 06 00 00 00 00 00 00 00",
+ )
+ dnskey_message.answer.append(dnskey_rrset)
+
+ yield DnsResponseSend(dnskey_message)
+
+ # Finish the AXFR transaction by sending the second SOA RRset.
+ yield DnsResponseSend(soa_message)
+
+ # This makes sure that the next SOA request causes a new zone transfer
+ self.soa_version += 1
+
+
+if __name__ == "__main__":
+ server = ControllableAsyncDnsServer(
+ default_aa=True, default_rcode=dns.rcode.NOERROR
+ )
+ server.install_control_command(ToggleResponsesCommand())
+ server.install_response_handler(AXFRServer())
+ server.run()
diff --git a/bin/tests/system/xfer/ns6/named.conf.in b/bin/tests/system/xfer/ns6/named.conf.in
index 142383c89a..63809448f0 100644
--- a/bin/tests/system/xfer/ns6/named.conf.in
+++ b/bin/tests/system/xfer/ns6/named.conf.in
@@ -83,3 +83,12 @@ zone "ixfr-too-big" {
primaries { 10.53.0.1; };
file "ixfr-too-big.bk";
};
+
+# GL#6004
+zone "private-dns-overrun" {
+ type secondary;
+ primaries { 10.53.0.9; };
+ file "private-dns-overrun.bk";
+ masterfile-format text; # force bug to be exercised
+ request-ixfr no; # ans9 supports only axfr
+};
diff --git a/bin/tests/system/xfer/tests.sh b/bin/tests/system/xfer/tests.sh
index a2c0adbc28..e08be175b7 100755
--- a/bin/tests/system/xfer/tests.sh
+++ b/bin/tests/system/xfer/tests.sh
@@ -622,5 +622,21 @@ if [ $tmp -eq 0 ]; then
fi
status=$((status + tmp))
+# def test_malformed_private_dns_identifier_overrun(ns6):
+# isctest.log.info(
+# "Check that a malformed PRIVATEDNS DNSKEY which overruns the record is rejected"
+# )
+# with ns6.watch_log_from_start(timeout=60) as watcher_transfer_completed:
+# watcher_transfer_completed.wait_for_line(
+# "zone private-dns-overrun/IN: zone transfer finished: unexpected end of input"
+# )
+n=$((n + 1))
+echo_i "Check that a malformed PRIVATEDNS DNSKEY which overruns the record is rejected ($n)"
+tmp=0
+nextpartreset ns6/named.run
+retry 60 wait_for_message "zone private-dns-overrun/IN: zone transfer finished: unexpected end of input" || tmp=1
+if test $tmp != 0; then echo_i "failed"; fi
+status=$((status + tmp))
+
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1
diff --git a/tests/dns/name_test.c b/tests/dns/name_test.c
index fb34dcace1..95f6598eb8 100644
--- a/tests/dns/name_test.c
+++ b/tests/dns/name_test.c
@@ -335,6 +335,35 @@ ISC_RUN_TEST_IMPL(fromregion) {
assert_false(dns_name_isabsolute(&name));
}
+ISC_RUN_TEST_IMPL(fromwire) {
+ dns_decompress_t dctx;
+ dns_fixedname_t fixed;
+ dns_name_t *name = dns_fixedname_initname(&fixed);
+ isc_buffer_t b;
+ unsigned char source[] = { 0x03, 'o', 'n', 'e', 0x00, 0x03,
+ 't', 'w', 'o', 0x00, 0x05, 't',
+ 'h', 'r', 'e', 'e', 0x00 };
+ isc_result_t result;
+
+ isc_buffer_init(&b, source, sizeof(source));
+ isc_buffer_add(&b, sizeof(source));
+ isc_buffer_setactive(&b, 10); /* names 'one.' and 'two.' */
+
+ /*
+ * We should only be able to read two names from the buffer
+ * as the active region has been set to cover only the first
+ * two.
+ */
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_STRICT);
+ dns_decompress_setmethods(&dctx, DNS_COMPRESS_NONE);
+ result = dns_name_fromwire(name, &b, &dctx, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_name_fromwire(name, &b, &dctx, 0, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = dns_name_fromwire(name, &b, &dctx, 0, NULL);
+ assert_int_not_equal(result, ISC_R_SUCCESS);
+}
+
/* is trust-anchor-telemetry test */
ISC_RUN_TEST_IMPL(istat) {
dns_fixedname_t fixed;
@@ -778,6 +807,7 @@ ISC_TEST_LIST_START
ISC_TEST_ENTRY(fullcompare)
ISC_TEST_ENTRY(compression)
ISC_TEST_ENTRY(fromregion)
+ISC_TEST_ENTRY(fromwire)
ISC_TEST_ENTRY(istat)
ISC_TEST_ENTRY(init)
ISC_TEST_ENTRY(invalidate)
diff --git a/tests/dns/rdata_test.c b/tests/dns/rdata_test.c
index 6354819d10..7f0df6e046 100644
--- a/tests/dns/rdata_test.c
+++ b/tests/dns/rdata_test.c
@@ -2199,6 +2199,27 @@ ISC_RUN_TEST_IMPL(key) {
check_rdata(NULL, wire_ok, NULL, false, dns_rdataclass_in,
dns_rdatatype_key, sizeof(dns_rdata_key_t));
+
+ /*
+ * A valid PRIVATEDNS record with an active region shorter than the
+ * actual record length. A bug in dns_name_fromwire meant that this
+ * was previously accepted.
+ */
+ dns_decompress_t dctx;
+ unsigned char key[] = { 0x00, 0x00, 0x00, 253, 0x07, 'e', 'x',
+ 'a', 'm', 'p', 'l', 'e', 0x00 };
+ unsigned char buf[sizeof(key)];
+ isc_buffer_t source, target;
+ isc_result_t result;
+
+ isc_buffer_init(&source, key, sizeof(key));
+ isc_buffer_add(&source, sizeof(key));
+ isc_buffer_setactive(&source, sizeof(key) - 1);
+ isc_buffer_init(&target, buf, sizeof(buf));
+ dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_ANY);
+ result = dns_rdata_fromwire(NULL, dns_rdataclass_in, dns_rdatatype_key,
+ &source, &dctx, 0, &target);
+ assert_int_not_equal(result, ISC_R_SUCCESS);
}
/*
--
2.55.0

View file

@ -0,0 +1,54 @@
From 7596cbc240b0492461943f7c34d040fb66a7554c Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 19 May 2026 15:00:17 +1000
Subject: [PATCH] Fix the yaml query zone name code in dnstap-read
When the buffer to read the query zone name was constructed
isc_buffer_setactive was not called. This is now needed as
dns_name_fromwire is being corrected to check the active region.
(cherry picked from commit a25522c28c46655a81d2bf1d96374c81d834b157)
(cherry picked from commit a5f1a9d0d2ec021618924b14202ac96ead8299c1)
Fix dns_name_fromwire to honour the active region
dns_name_fromwire was not honouring the source buffer's active
region when reading names from the wire. This allowed malformed
records to be accepted when they shouldn't have been. This has
been corrected.
(cherry picked from commit 7c4f07a7ef6b571073327b02209df7f75b9363ff)
(cherry picked from commit e73b70a64453e7d97a11cb5f0afe8bb02d34aaf8)
---
bin/tools/dnstap-read.c | 1 +
lib/dns/name.c | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/bin/tools/dnstap-read.c b/bin/tools/dnstap-read.c
index a1d0243a1a..bb78ae12b1 100644
--- a/bin/tools/dnstap-read.c
+++ b/bin/tools/dnstap-read.c
@@ -298,6 +298,7 @@ print_yaml(dns_dtdata_t *dt) {
isc_buffer_init(&b, m->query_zone.data, m->query_zone.len);
isc_buffer_add(&b, m->query_zone.len);
+ isc_buffer_setactive(&b, m->query_zone.len);
dns_decompress_init(&dctx, -1, DNS_DECOMPRESS_NONE);
result = dns_name_fromwire(name, &b, &dctx, 0, NULL);
diff --git a/lib/dns/name.c b/lib/dns/name.c
index cc0e30e5b5..2ce868a2ba 100644
--- a/lib/dns/name.c
+++ b/lib/dns/name.c
@@ -1833,7 +1833,7 @@ dns_name_fromwire(dns_name_t *const name, isc_buffer_t *const source,
* The amount of the source we consumed is set once.
*/
const uint8_t *const source_buf = isc_buffer_base(source);
- const uint8_t *const source_max = isc_buffer_used(source);
+ const uint8_t *const source_max = isc_buffer_active(source);
const uint8_t *const start = isc_buffer_current(source);
const uint8_t *marker = start;
const uint8_t *cursor = start;
--
2.55.0

View file

@ -0,0 +1,69 @@
From cde8bb21e09205a7bd1f41fd07ed011fc80d8d71 Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Fri, 10 Apr 2026 10:24:06 +1000
Subject: [PATCH] Fix TTL extraction from A/AAAA record
(cherry picked from commit 89c86e338db2492b92e6618c586f146c6928dc6d)
(cherry picked from commit adc8285d23e2eac6ec463f5dbc5a9596fdd36c60)
Check rpz name too long wildcard CNAME expansion handling
(cherry picked from commit 9345394e2097031b55b3ef34ceaadf5a7ebbeef2)
(cherry picked from commit 095b11f20f911f5b8059bdc349b256d6c64ece30)
---
bin/tests/system/rpz/ns2/tld2.db | 2 ++
bin/tests/system/rpz/ns4/tld4.db | 2 ++
bin/tests/system/rpz/tests.sh | 7 +++++--
3 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/bin/tests/system/rpz/ns2/tld2.db b/bin/tests/system/rpz/ns2/tld2.db
index c6f2556db5..c091ee27b7 100644
--- a/bin/tests/system/rpz/ns2/tld2.db
+++ b/bin/tests/system/rpz/ns2/tld2.db
@@ -123,3 +123,5 @@ a7-1 A 192.168.7.1
a7-2 A 192.168.7.2
TXT "a7-2 tld2 text"
+
+*.wild A 192.168.9.1
diff --git a/bin/tests/system/rpz/ns4/tld4.db b/bin/tests/system/rpz/ns4/tld4.db
index fca419c6dd..8accd76baf 100644
--- a/bin/tests/system/rpz/ns4/tld4.db
+++ b/bin/tests/system/rpz/ns4/tld4.db
@@ -59,6 +59,8 @@ a3-6.tld2 A 56.56.56.56
a3-7.sub1.tld2 A 57.57.57.57
+*.wild.sub1.tld2 A 57.57.57.57
+
a3-8.tld2 A 58.58.58.58
a3-9.sub9.tld2 A 59.59.59.59
diff --git a/bin/tests/system/rpz/tests.sh b/bin/tests/system/rpz/tests.sh
index 87e4118ca3..5297437694 100644
--- a/bin/tests/system/rpz/tests.sh
+++ b/bin/tests/system/rpz/tests.sh
@@ -391,7 +391,7 @@ addr() {
digcmd $2 >$DIGNM
#ckalive "$2" "server crashed by 'dig $2'" || return 1
ADDR_ESC=$(echo "$ADDR" | sed -e 's/\./\\./g')
- ADDR_TTL=$(sed -n -e "s/^[-.a-z0-9]\{1,\}[ ]*\([0-9]*\) IN AA* ${ADDR_ESC}\$/\1/p" $DIGNM)
+ ADDR_TTL=$(sed -n -e "s/^[-.a-z0-9]\{1,\}[ ]*\([0-9]*\)[ ]IN[ ]AA*[ ]${ADDR_ESC}\$/\1/p" $DIGNM)
if test -z "$ADDR_TTL"; then
setret "'dig $2' wrong; no address $ADDR record in $DIGNM"
return 0
@@ -516,7 +516,10 @@ nochange TCP a3-9.tld2 # 33 tcp-only
here x.servfail <<'EOF' # 34 qname-wait-recurse yes
;; status: SERVFAIL, x
EOF
-addr 35.35.35.35 "x.servfail @$ns5" # 35 qname-wait-recurse no
+addr 35.35.35.35 "x.servfail @$ns5" # 35 qname-wait-recurse no
+here aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.wild.sub1.tld2 <<'EOF' # 36 wildcard CNAME name to long
+ ;; status: YXDOMAIN, x
+EOF
end_group
ckstats $ns3 test1 ns3 22
ckstats $ns5 test1 ns5 1
--
2.55.0

View file

@ -0,0 +1,31 @@
From 49f4cc4e93f14f1d5b6a472124e6aa457167fede Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Fri, 10 Apr 2026 10:26:14 +1000
Subject: [PATCH] Properly handle rpz name to long wildcard expansion
Previously a self referential CNAME and the original address
record were returned. We now return a YXDOMAIN response.
(cherry picked from commit cfc4c4f69870ce492deaaa429453563d1621ded3)
(cherry picked from commit dc328a199f96222e0c30cc20b7b795bfc2c9b2e4)
---
lib/ns/query.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/ns/query.c b/lib/ns/query.c
index d3a10be9ba..3bd7daf79c 100644
--- a/lib/ns/query.c
+++ b/lib/ns/query.c
@@ -7591,7 +7591,8 @@ query_rpzcname(query_ctx_t *qctx, dns_name_t *cname) {
qctx->fname, NULL);
if (result == DNS_R_NAMETOOLONG) {
client->message->rcode = dns_rcode_yxdomain;
- } else if (result != ISC_R_SUCCESS) {
+ }
+ if (result != ISC_R_SUCCESS) {
return result;
}
} else {
--
2.55.0

View file

@ -0,0 +1,280 @@
From d7e1f4495d6bac8c29b332e04e9b27140339375b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@sury.org>
Date: Tue, 23 Jun 2026 10:59:38 +0200
Subject: [PATCH] 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. The reference is
incremented when an rdataset is bound to the header and decremented when
the rdataset is disassociated, so a stale slabheader can be removed from
the node's down chain as soon as its own reference count reaches zero,
instead of waiting for the whole qpcnode to become unreferenced.
Building on that, clean up the ancient headers eagerly: mark_ancient()
is made idempotent, releases the header's own (container) reference and
reaps the stale headers from the node's down chain as soon as their
references reach zero. A header evicted over the per-name type limit is
expired only after the new rdataset has been bound, so the bind's
increment always precedes mark_ancient()'s decrement.
Because a header can now be reclaimed independently of its node, the
rdataset iterators must keep the header they are positioned on alive:
each iterator takes a reference on its current header and releases it
when it advances or is destroyed. Iteration otherwise stays lazy and
re-reads the node on every step, so it still observes records added to
the node while the iterator is live, as zone signing requires.
The slab headers are shared with the zone databases, so the matching
increment is added to every bind path. The noqname/closest proofs hand
out rdatasets backed by bare slabs that have no header, so they are
given a separate dns_rdataproof_rdatasetmethods that leaves the
reference count untouched.
(cherry picked from commit 2dabf117e1264fd13fb33096f87e78a039fd1c6c)
(cherry picked from commit 231b1ca3edfb26389e1af39181aa6b4413e87ec4)
---
bin/tests/system/reclimit/tests.sh | 4 +-
lib/dns/include/dns/rdataslab.h | 1 +
lib/dns/rbtdb.c | 77 +++++++++++++++++++++++++-----
3 files changed, 69 insertions(+), 13 deletions(-)
diff --git a/bin/tests/system/reclimit/tests.sh b/bin/tests/system/reclimit/tests.sh
index c15225488f..55ccac7759 100644
--- a/bin/tests/system/reclimit/tests.sh
+++ b/bin/tests/system/reclimit/tests.sh
@@ -338,13 +338,13 @@ echo_i "checking that NXDOMAIN names over the max-types-per-name limit don't get
# Query for 10 NXDOMAIN types
for ntype in $(seq 65270 65279); do
- check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 0 || ret=1
+ check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 120 || ret=1
done
# Wait at least 1 second
sleep 1
# Query for 10 NXDOMAIN types again - these should not be cached
for ntype in $(seq 65270 65279); do
- check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA 0 || ret=1
+ check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA 120 || ret=1
done
if [ $ret -ne 0 ]; then echo_i "failed"; fi
diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h
index 5729c004ca..6bd3b5997d 100644
--- a/lib/dns/include/dns/rdataslab.h
+++ b/lib/dns/include/dns/rdataslab.h
@@ -44,6 +44,7 @@
#include <stdbool.h>
#include <isc/lang.h>
+#include <isc/refcount.h>
#include <dns/types.h>
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
index 62bc97d783..0b8547950f 100644
--- a/lib/dns/rbtdb.c
+++ b/lib/dns/rbtdb.c
@@ -158,6 +158,7 @@ struct noqname {
};
typedef struct rdatasetheader {
+ isc_refcount_t references;
/*%
* Locked by the owning node's lock.
*/
@@ -1447,6 +1448,7 @@ init_rdataset(dns_rbtdb_t *rbtdb, rdatasetheader_t *h) {
h->heap_index = 0;
atomic_init(&h->attributes, 0);
atomic_init(&h->last_refresh_fail_ts, 0);
+ isc_refcount_init(&h->references, 1);
STATIC_ASSERT(sizeof(h->attributes) == 2,
"The .attributes field of rdatasetheader_t needs to be "
@@ -1559,6 +1561,9 @@ rollback_node(dns_rbtnode_t *node, rbtdb_serial_t serial) {
}
}
+static void
+clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx, rdatasetheader_t *top);
+
static void
mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
uint_least16_t attributes = atomic_load_acquire(&header->attributes);
@@ -1584,8 +1589,12 @@ mark_header_ancient(dns_rbtdb_t *rbtdb, rdatasetheader_t *header) {
update_rrsetstats(rbtdb, header->type, attributes, false);
header->node->dirty = 1;
+ isc_refcount_decrement(&header->references);
+
/* Increment the stats counter for the ancient RRtype. */
update_rrsetstats(rbtdb, header->type, newattributes, true);
+
+ clean_stale_headers(rbtdb, rbtdb->common.mctx, header);
}
static void
@@ -1621,12 +1630,19 @@ static void
clean_stale_headers(dns_rbtdb_t *rbtdb, isc_mem_t *mctx,
rdatasetheader_t *top) {
rdatasetheader_t *d, *down_next;
+ rdatasetheader_t *down_parent = top;
for (d = top->down; d != NULL; d = down_next) {
down_next = d->down;
- free_rdataset(rbtdb, mctx, d);
+ d->next = down_parent;
+
+ if (isc_refcount_current(&d->references) == 0) {
+ free_rdataset(rbtdb, mctx, d);
+ down_parent->down = down_next;
+ } else {
+ down_parent = d;
+ }
}
- top->down = NULL;
}
static void
@@ -1642,6 +1658,7 @@ clean_cache_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) {
for (current = node->data; current != NULL; current = top_next) {
top_next = current->next;
clean_stale_headers(rbtdb, mctx, current);
+ INSIST(current->down == NULL);
/*
* If current is nonexistent, ancient, or stale and
* we are not keeping stale, we can clean it up.
@@ -3114,6 +3131,8 @@ bind_rdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, rdatasetheader_t *header,
return;
}
+ isc_refcount_increment(&header->references);
+
dns__rbtnode_acquire(rbtdb, node, locktype);
INSIST(rdataset->methods == NULL); /* We must be disassociated. */
@@ -6307,6 +6326,7 @@ add32(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, const dns_name_t *nodename,
bool header_nx;
bool newheader_nx;
bool merge;
+ bool do_expireheader = false;
dns_rdatatype_t rdtype, covers;
rbtdb_rdatatype_t negtype, sigtype;
dns_trust_t trust;
@@ -6856,6 +6876,7 @@ find_header:
}
if (IS_CACHE(rbtdb) && overmaxtype(rbtdb, ntypes)) {
+ do_expireheader = true;
if (expireheader == NULL) {
expireheader = newheader;
}
@@ -6869,15 +6890,6 @@ find_header:
*/
expireheader = newheader;
}
-
- set_ttl(rbtdb, expireheader, 0);
- mark_header_ancient(rbtdb, expireheader);
- /*
- * FIXME: In theory, we should mark the RRSIG
- * and the header at the same time, but there is
- * no direct link between those two header, so
- * we would have to check the whole list again.
- */
}
}
}
@@ -6901,6 +6913,15 @@ find_header:
isc_rwlocktype_write, addedrdataset);
}
+ /*
+ * We need to delay the expiration of the header until we are bound to
+ * it to prevent decrement-then-increment on the header references.
+ */
+ if (do_expireheader) {
+ set_ttl(rbtdb, expireheader, 0);
+ mark_header_ancient(rbtdb, expireheader);
+ }
+
return ISC_R_SUCCESS;
}
@@ -8692,6 +8713,12 @@ rdataset_disassociate(dns_rdataset_t *rdataset) {
dns_db_t *db = rdataset->private1;
dns_dbnode_t *node = rdataset->private2;
+ if (rdataset->methods == &rdataset_methods) {
+ rdatasetheader_t *header = rdataset->private3;
+ header--;
+ isc_refcount_decrement(&header->references);
+ }
+
detachnode(db, &node);
}
@@ -8806,6 +8833,11 @@ rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target) {
dns_dbnode_t *cloned_node = NULL;
attachnode(db, node, &cloned_node);
+ if (source->methods == &rdataset_methods) {
+ rdatasetheader_t *header = source->private3;
+ header--;
+ isc_refcount_increment(&header->references);
+ }
INSIST(!ISC_LINK_LINKED(target, link));
*target = *source;
ISC_LINK_INIT(target, link);
@@ -8969,6 +9001,11 @@ rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp) {
rbtiterator = (rbtdb_rdatasetiter_t *)(*iteratorp);
+ if (rbtiterator->current != NULL) {
+ isc_refcount_decrement(&rbtiterator->current->references);
+ rbtiterator->current = NULL;
+ }
+
if (rbtiterator->common.version != NULL) {
closeversion(rbtiterator->common.db,
&rbtiterator->common.version, false);
@@ -9046,9 +9083,18 @@ rdatasetiter_first(dns_rdatasetiter_t *iterator) {
}
}
+ if (header != NULL) {
+ isc_refcount_increment0(&header->references);
+ }
+
NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
isc_rwlocktype_read);
+ if (rbtiterator->current != NULL) {
+ isc_refcount_decrement(&rbtiterator->current->references);
+ rbtiterator->current = NULL;
+ }
+
rbtiterator->current = header;
if (header == NULL) {
@@ -9140,9 +9186,18 @@ rdatasetiter_next(dns_rdatasetiter_t *iterator) {
}
}
+ if (header != NULL) {
+ isc_refcount_increment0(&header->references);
+ }
+
NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock,
isc_rwlocktype_read);
+ if (rbtiterator->current != NULL) {
+ isc_refcount_decrement(&rbtiterator->current->references);
+ rbtiterator->current = NULL;
+ }
+
rbtiterator->current = header;
if (header == NULL) {
--
2.55.0

View file

@ -0,0 +1,144 @@
From b08e0876639ab9f3dae3813202861fd1098f2611 Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 14 Apr 2026 13:46:22 +1000
Subject: [PATCH] Test RRSIG record parsing
In particular test that labels and signer fields are consistent.
(cherry picked from commit 5a95e64731afe63d348d272cc4d3b2f9847150c2)
(cherry picked from commit 19e496ca260b6a756ae1378e8ebcbdb666b7d9ed)
---
tests/dns/rdata_test.c | 110 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 110 insertions(+)
diff --git a/tests/dns/rdata_test.c b/tests/dns/rdata_test.c
index 7f0df6e046..c704d98ed1 100644
--- a/tests/dns/rdata_test.c
+++ b/tests/dns/rdata_test.c
@@ -2504,6 +2504,115 @@ ISC_RUN_TEST_IMPL(rkey) {
dns_rdatatype_rkey, sizeof(dns_rdata_rkey_t));
}
+ISC_RUN_TEST_IMPL(rrsig) {
+ text_ok_t text_ok[] = {
+ TEXT_VALID("SOA 8 0 86400 20260426170000 20260413160000 54393 "
+ ". "
+ "tFbcoVP8MnpecUquJ/aj+XeNgV7ts9GSHVkXaXRJrJ/"
+ "TEkOZApVG0F6E "
+ "9sYpxGk2ItweLL43ujioGj0HWwZDRR+vbur+O/"
+ "dIdheiig1VvU+9HXLi "
+ "QOViY9Kc64ixdyJhYCC5K+bO1qsHxd+"
+ "KJXOaxyHbqchYkDFy4PL6qftE "
+ "VaLkueRgjXgOsq/"
+ "NxvCXDgAa5xy0+3Sl0myxIs8rJ5KeXfJQFe7qxgaw "
+ "VjJsJTKw8neOTw2rQfLaigWu2LIWw+"
+ "IyVrLjZJdLqGkiLBGd1w4X3U12 "
+ "fFxoY3eqzNgBEtduoGKPZ/"
+ "NpP9cuKJORJ18283aV8hR4WO91VR0q1zcM jLwqUg=="),
+ /* labels too short for signer */
+ TEXT_INVALID("SOA 8 0 86400 20260426170000 20260413160000 "
+ "54393 example. "
+ "tFbcoVP8MnpecUquJ/aj+XeNgV7ts9GSHVkXaXRJrJ/"
+ "TEkOZApVG0F6E "
+ "9sYpxGk2ItweLL43ujioGj0HWwZDRR+vbur+O/"
+ "dIdheiig1VvU+9HXLi "
+ "QOViY9Kc64ixdyJhYCC5K+bO1qsHxd+"
+ "KJXOaxyHbqchYkDFy4PL6qftE "
+ "VaLkueRgjXgOsq/"
+ "NxvCXDgAa5xy0+3Sl0myxIs8rJ5KeXfJQFe7qxgaw "
+ "VjJsJTKw8neOTw2rQfLaigWu2LIWw+"
+ "IyVrLjZJdLqGkiLBGd1w4X3U12 "
+ "fFxoY3eqzNgBEtduoGKPZ/"
+ "NpP9cuKJORJ18283aV8hR4WO91VR0q1zcM jLwqUg=="),
+ /*
+ * Sentinel.
+ */
+ TEXT_SENTINEL()
+ };
+ wire_ok_t wire_ok[] = {
+ WIRE_VALID(0x00, 0x06, 0x08, 0x00, 0x00, 0x01, 0x51, 0x80, 0x69,
+ 0xee, 0x44, 0x90, 0x69, 0xdd, 0x13, 0x00, 0xd4, 0x79,
+ 0x00, 0xb4, 0x56, 0xdc, 0xa1, 0x53, 0xfc, 0x32, 0x7a,
+ 0x5e, 0x71, 0x4a, 0xae, 0x27, 0xf6, 0xa3, 0xf9, 0x77,
+ 0x8d, 0x81, 0x5e, 0xed, 0xb3, 0xd1, 0x92, 0x1d, 0x59,
+ 0x17, 0x69, 0x74, 0x49, 0xac, 0x9f, 0xd3, 0x12, 0x43,
+ 0x99, 0x02, 0x95, 0x46, 0xd0, 0x5e, 0x84, 0xf6, 0xc6,
+ 0x29, 0xc4, 0x69, 0x36, 0x22, 0xdc, 0x1e, 0x2c, 0xbe,
+ 0x37, 0xba, 0x38, 0xa8, 0x1a, 0x3d, 0x07, 0x5b, 0x06,
+ 0x43, 0x45, 0x1f, 0xaf, 0x6e, 0xea, 0xfe, 0x3b, 0xf7,
+ 0x48, 0x76, 0x17, 0xa2, 0x8a, 0x0d, 0x55, 0xbd, 0x4f,
+ 0xbd, 0x1d, 0x72, 0xe2, 0x40, 0xe5, 0x62, 0x63, 0xd2,
+ 0x9c, 0xeb, 0x88, 0xb1, 0x77, 0x22, 0x61, 0x60, 0x20,
+ 0xb9, 0x2b, 0xe6, 0xce, 0xd6, 0xab, 0x07, 0xc5, 0xdf,
+ 0x8a, 0x25, 0x73, 0x9a, 0xc7, 0x21, 0xdb, 0xa9, 0xc8,
+ 0x58, 0x90, 0x31, 0x72, 0xe0, 0xf2, 0xfa, 0xa9, 0xfb,
+ 0x44, 0x55, 0xa2, 0xe4, 0xb9, 0xe4, 0x60, 0x8d, 0x78,
+ 0x0e, 0xb2, 0xaf, 0xcd, 0xc6, 0xf0, 0x97, 0x0e, 0x00,
+ 0x1a, 0xe7, 0x1c, 0xb4, 0xfb, 0x74, 0xa5, 0xd2, 0x6c,
+ 0xb1, 0x22, 0xcf, 0x2b, 0x27, 0x92, 0x9e, 0x5d, 0xf2,
+ 0x50, 0x15, 0xee, 0xea, 0xc6, 0x06, 0xb0, 0x56, 0x32,
+ 0x6c, 0x25, 0x32, 0xb0, 0xf2, 0x77, 0x8e, 0x4f, 0x0d,
+ 0xab, 0x41, 0xf2, 0xda, 0x8a, 0x05, 0xae, 0xd8, 0xb2,
+ 0x16, 0xc3, 0xe2, 0x32, 0x56, 0xb2, 0xe3, 0x64, 0x97,
+ 0x4b, 0xa8, 0x69, 0x22, 0x2c, 0x11, 0x9d, 0xd7, 0x0e,
+ 0x17, 0xdd, 0x4d, 0x76, 0x7c, 0x5c, 0x68, 0x63, 0x77,
+ 0xaa, 0xcc, 0xd8, 0x01, 0x12, 0xd7, 0x6e, 0xa0, 0x62,
+ 0x8f, 0x67, 0xf3, 0x69, 0x3f, 0xd7, 0x2e, 0x28, 0x93,
+ 0x91, 0x27, 0x5f, 0x36, 0xf3, 0x76, 0x95, 0xf2, 0x14,
+ 0x78, 0x58, 0xef, 0x75, 0x55, 0x1d, 0x2a, 0xd7, 0x37,
+ 0x0c, 0x8c, 0xbc, 0x2a, 0x52),
+ /* labels too short for signer */
+ WIRE_INVALID(
+ 0x00, 0x06, 0x08, 0x00, 0x00, 0x01, 0x51, 0x80, 0x69,
+ 0xee, 0x44, 0x90, 0x69, 0xdd, 0x13, 0x00, 0xd4, 0x79,
+ 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x00,
+ 0xb4, 0x56, 0xdc, 0xa1, 0x53, 0xfc, 0x32, 0x7a, 0x5e,
+ 0x71, 0x4a, 0xae, 0x27, 0xf6, 0xa3, 0xf9, 0x77, 0x8d,
+ 0x81, 0x5e, 0xed, 0xb3, 0xd1, 0x92, 0x1d, 0x59, 0x17,
+ 0x69, 0x74, 0x49, 0xac, 0x9f, 0xd3, 0x12, 0x43, 0x99,
+ 0x02, 0x95, 0x46, 0xd0, 0x5e, 0x84, 0xf6, 0xc6, 0x29,
+ 0xc4, 0x69, 0x36, 0x22, 0xdc, 0x1e, 0x2c, 0xbe, 0x37,
+ 0xba, 0x38, 0xa8, 0x1a, 0x3d, 0x07, 0x5b, 0x06, 0x43,
+ 0x45, 0x1f, 0xaf, 0x6e, 0xea, 0xfe, 0x3b, 0xf7, 0x48,
+ 0x76, 0x17, 0xa2, 0x8a, 0x0d, 0x55, 0xbd, 0x4f, 0xbd,
+ 0x1d, 0x72, 0xe2, 0x40, 0xe5, 0x62, 0x63, 0xd2, 0x9c,
+ 0xeb, 0x88, 0xb1, 0x77, 0x22, 0x61, 0x60, 0x20, 0xb9,
+ 0x2b, 0xe6, 0xce, 0xd6, 0xab, 0x07, 0xc5, 0xdf, 0x8a,
+ 0x25, 0x73, 0x9a, 0xc7, 0x21, 0xdb, 0xa9, 0xc8, 0x58,
+ 0x90, 0x31, 0x72, 0xe0, 0xf2, 0xfa, 0xa9, 0xfb, 0x44,
+ 0x55, 0xa2, 0xe4, 0xb9, 0xe4, 0x60, 0x8d, 0x78, 0x0e,
+ 0xb2, 0xaf, 0xcd, 0xc6, 0xf0, 0x97, 0x0e, 0x00, 0x1a,
+ 0xe7, 0x1c, 0xb4, 0xfb, 0x74, 0xa5, 0xd2, 0x6c, 0xb1,
+ 0x22, 0xcf, 0x2b, 0x27, 0x92, 0x9e, 0x5d, 0xf2, 0x50,
+ 0x15, 0xee, 0xea, 0xc6, 0x06, 0xb0, 0x56, 0x32, 0x6c,
+ 0x25, 0x32, 0xb0, 0xf2, 0x77, 0x8e, 0x4f, 0x0d, 0xab,
+ 0x41, 0xf2, 0xda, 0x8a, 0x05, 0xae, 0xd8, 0xb2, 0x16,
+ 0xc3, 0xe2, 0x32, 0x56, 0xb2, 0xe3, 0x64, 0x97, 0x4b,
+ 0xa8, 0x69, 0x22, 0x2c, 0x11, 0x9d, 0xd7, 0x0e, 0x17,
+ 0xdd, 0x4d, 0x76, 0x7c, 0x5c, 0x68, 0x63, 0x77, 0xaa,
+ 0xcc, 0xd8, 0x01, 0x12, 0xd7, 0x6e, 0xa0, 0x62, 0x8f,
+ 0x67, 0xf3, 0x69, 0x3f, 0xd7, 0x2e, 0x28, 0x93, 0x91,
+ 0x27, 0x5f, 0x36, 0xf3, 0x76, 0x95, 0xf2, 0x14, 0x78,
+ 0x58, 0xef, 0x75, 0x55, 0x1d, 0x2a, 0xd7, 0x37, 0x0c,
+ 0x8c, 0xbc, 0x2a, 0x52),
+
+ WIRE_SENTINEL()
+ };
+ check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in,
+ dns_rdatatype_rrsig, sizeof(dns_rdata_rrsig_t));
+}
+
ISC_RUN_TEST_IMPL(resinfo) {
text_ok_t text_ok[] = {
TEXT_VALID_CHANGED("qnamemin exterr=15,16,17 "
@@ -3357,6 +3466,7 @@ ISC_TEST_ENTRY(nsec3)
ISC_TEST_ENTRY(nxt)
ISC_TEST_ENTRY(resinfo)
ISC_TEST_ENTRY(rkey)
+ISC_TEST_ENTRY(rrsig)
ISC_TEST_ENTRY(sshfp)
ISC_TEST_ENTRY(wallet)
ISC_TEST_ENTRY(wks)
--
2.55.0

View file

@ -0,0 +1,267 @@
From 7a8a1f74c742e98fb5e105b013e7c2bd7af4a76c Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 14 Apr 2026 15:14:06 +1000
Subject: [PATCH] Don't sign out of zone records in dnssec-signzone
dnssec-signzone was signing extraneous records that were not within
the namespace of the zone. This no longer occurs.
(cherry picked from commit e45c9af7051421fd370f20ba8325199c606223fd)
Don't sign out of zone records in dnssec-signzone
dnssec-signzone was signing extraneous records that were not within
the namespace of the zone. This no longer occurs.
(cherry picked from commit e45c9af7051421fd370f20ba8325199c606223fd)
(cherry picked from commit 1a4986e2533f87e80eb21da3f06708d335aff1e2)
Invalid signed wildcard records were being accepted
An RRSIG whose Labels field indicates fewer labels than its signer
name requires was being accepted. When such a record covers a
wildcard, the validator reconstructs a wildcard owner name above the
signer's zone and caches it as secure. RFC 8198 cache synthesis
(synth-from-dnssec) then serves that forged wildcard for unrelated
names, poisoning the cache.
These records are now rejected, both when an RRSIG is parsed and when
its signature is verified.
(cherry picked from commit 084ca5ee10515e461d46b63df9660b8394bc7de9)
(cherry picked from commit 15089066b15f826d7487c3d160b5872820f84b83)
---
bin/dnssec/dnssec-signzone.c | 5 ++++
lib/dns/dnssec.c | 43 +++++++++++++++++++++++---------
lib/dns/rdata/generic/rrsig_46.c | 37 ++++++++++++++++++++-------
3 files changed, 64 insertions(+), 21 deletions(-)
diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c
index 73855e6284..9e3a48a592 100644
--- a/bin/dnssec/dnssec-signzone.c
+++ b/bin/dnssec/dnssec-signzone.c
@@ -1643,6 +1643,11 @@ assignwork(isc_task_t *task, isc_task_t *worker) {
dns_db_detachnode(gdb, &node);
goto next;
}
+ if (!dns_name_issubdomain(name, gorigin)) {
+ dumpnode(name, node);
+ dns_db_detachnode(gdb, &node);
+ goto next;
+ }
/*
* Sort the zone data from the glue and out-of-zone data.
* For NSEC zones nodes with zone data have NSEC records.
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index c7e922437c..b12529b5d5 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -130,11 +130,11 @@ dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata,
isc_buffer_t b;
isc_region_t r;
- INSIST(name != NULL);
- INSIST(rdata != NULL);
- INSIST(mctx != NULL);
- INSIST(key != NULL);
- INSIST(*key == NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(rdata != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(key != NULL);
+ REQUIRE(*key == NULL);
REQUIRE(rdata->type == dns_rdatatype_key ||
rdata->type == dns_rdatatype_dnskey);
@@ -187,12 +187,14 @@ dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
isc_result_t ret;
isc_buffer_t *databuf = NULL;
char data[256 + 8];
+ unsigned int labels;
unsigned int sigsize;
dns_fixedname_t fnewname;
dns_fixedname_t fsigner;
REQUIRE(name != NULL);
- REQUIRE(dns_name_countlabels(name) <= 255);
+ labels = dns_name_countlabels(name);
+ REQUIRE(labels <= 255 && labels > 0);
REQUIRE(set != NULL);
REQUIRE(key != NULL);
REQUIRE(inception != NULL);
@@ -221,7 +223,7 @@ dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
sig.covered = set->type;
sig.algorithm = dst_key_alg(key);
- sig.labels = dns_name_countlabels(name) - 1;
+ sig.labels = labels - 1;
if (dns_name_iswildcard(name)) {
sig.labels--;
}
@@ -365,10 +367,13 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
isc_result_t ret;
unsigned char data[300];
dst_context_t *ctx = NULL;
- int labels = 0;
+ unsigned int labels;
+ unsigned int siglabels;
bool downcase = false;
REQUIRE(name != NULL);
+ labels = dns_name_countlabels(name);
+ REQUIRE(labels > 0);
REQUIRE(set != NULL);
REQUIRE(key != NULL);
REQUIRE(mctx != NULL);
@@ -383,6 +388,21 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
return DNS_R_SIGINVALID;
}
+ /*
+ * The RRSIG labels field can't indicate fewer labels than the
+ * signer. Also the labels shouldn't be greater than that of
+ * the owner name.
+ *
+ * sig.labels doesn't include the root label, so add 1 to account
+ * for it.
+ */
+ siglabels = sig.labels + 1;
+ if (siglabels < dns_name_countlabels(&sig.signer) || siglabels > labels)
+ {
+ inc_stat(dns_dnssecstats_fail);
+ return DNS_R_SIGINVALID;
+ }
+
if (isc_serial_lt(sig.timeexpire, sig.timesigned)) {
inc_stat(dns_dnssecstats_fail);
return DNS_R_SIGINVALID;
@@ -449,10 +469,9 @@ again:
* If the name is an expanded wildcard, use the wildcard name.
*/
dns_fixedname_init(&fnewname);
- labels = dns_name_countlabels(name) - 1;
RUNTIME_CHECK(dns_name_downcase(name, dns_fixedname_name(&fnewname),
NULL) == ISC_R_SUCCESS);
- if (labels - sig.labels > 0) {
+ if (labels > siglabels) {
dns_name_split(dns_fixedname_name(&fnewname), sig.labels + 1,
NULL, dns_fixedname_name(&fnewname));
}
@@ -463,7 +482,7 @@ again:
* Create an envelope for each rdata: <name|type|class|ttl>.
*/
isc_buffer_init(&envbuf, data, sizeof(data));
- if (labels - sig.labels > 0) {
+ if (labels > siglabels) {
isc_buffer_putuint8(&envbuf, 1);
isc_buffer_putuint8(&envbuf, '*');
memmove(data + 2, r.base, r.length);
@@ -559,7 +578,7 @@ cleanup_struct:
inc_stat(dns_dnssecstats_fail);
}
- if (ret == ISC_R_SUCCESS && labels - sig.labels > 0) {
+ if (ret == ISC_R_SUCCESS && labels > siglabels) {
if (wild != NULL) {
RUNTIME_CHECK(dns_name_concatenate(
dns_wildcardname,
diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c
index 10bc039e93..4cf4259c2b 100644
--- a/lib/dns/rdata/generic/rrsig_46.c
+++ b/lib/dns/rdata/generic/rrsig_46.c
@@ -23,12 +23,12 @@
static isc_result_t
fromtext_rrsig(ARGS_FROMTEXT) {
isc_token_t token;
- unsigned char c;
+ unsigned char alg, labels;
long i;
dns_rdatatype_t covered;
- char *e;
+ char *e = NULL;
isc_result_t result;
- dns_name_t name;
+ dns_name_t signer;
isc_buffer_t buffer;
uint32_t time_signed, time_expire;
@@ -61,8 +61,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
*/
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- RETTOK(dns_secalg_fromtext(&c, &token.value.as_textregion));
- RETERR(mem_tobuffer(target, &c, 1));
+ RETTOK(dns_secalg_fromtext(&alg, &token.value.as_textregion));
+ RETERR(mem_tobuffer(target, &alg, 1));
/*
* Labels.
@@ -72,8 +72,8 @@ fromtext_rrsig(ARGS_FROMTEXT) {
if (token.value.as_ulong > 0xffU) {
RETTOK(ISC_R_RANGE);
}
- c = (unsigned char)token.value.as_ulong;
- RETERR(mem_tobuffer(target, &c, 1));
+ labels = (unsigned char)token.value.as_ulong;
+ RETERR(mem_tobuffer(target, &labels, 1));
/*
* Original ttl.
@@ -144,12 +144,20 @@ fromtext_rrsig(ARGS_FROMTEXT) {
*/
RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string,
false));
- dns_name_init(&name, NULL);
+ dns_name_init(&signer, NULL);
buffer_fromregion(&buffer, &token.value.as_region);
if (origin == NULL) {
origin = dns_rootname;
}
- RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target));
+ RETTOK(dns_name_fromtext(&signer, &buffer, origin, options, target));
+
+ /*
+ * (RRSIG labels doesn't include the root label, so add one
+ * to normalize it before checking against the signer.)
+ */
+ if ((unsigned int)(labels + 1) < dns_name_countlabels(&signer)) {
+ RETTOK(ISC_R_RANGE);
+ }
/*
* Sig.
@@ -278,6 +286,7 @@ static isc_result_t
fromwire_rrsig(ARGS_FROMWIRE) {
isc_region_t sr;
dns_name_t name;
+ unsigned char labels;
REQUIRE(type == dns_rdatatype_rrsig);
@@ -300,6 +309,8 @@ fromwire_rrsig(ARGS_FROMWIRE) {
return ISC_R_UNEXPECTEDEND;
}
+ labels = sr.base[3];
+
isc_buffer_forward(source, 18);
RETERR(mem_tobuffer(target, sr.base, 18));
@@ -309,6 +320,14 @@ fromwire_rrsig(ARGS_FROMWIRE) {
dns_name_init(&name, NULL);
RETERR(dns_name_fromwire(&name, source, dctx, options, target));
+ /*
+ * (RRSIG labels doesn't include the root label, so add one
+ * to normalize it before checking against the signer.)
+ */
+ if ((unsigned int)(labels + 1) < dns_name_countlabels(&name)) {
+ RETERR(DNS_R_FORMERR);
+ }
+
/*
* Sig.
*/
--
2.55.0

View file

@ -0,0 +1,435 @@
From a31296b120efc985fb1fc3c932882e965156473b Mon Sep 17 00:00:00 2001
From: Colin Vidal <colin@isc.org>
Date: Mon, 15 Jun 2026 11:34:08 +0200
Subject: [PATCH] Reproducer for #5946 (assertion in some CNAME/DNAME queries)
Add a system test reproducing the issue reported by #5946, which
is also CVE-2026-12617. There are two scenarios:
- A client send queries for a DNAME and A record to the resolver (ns3),
and the authoritative server (ans2) responds positively to the A query
but delay the DNAME response and respond later negatively;
- A client send queries for a CNAME and A record to the resolver (ns3),
and the authoritative server (ans2) responds positively to the A query
but delay the CNAME response and respond later with a self-referential
CNAME.
The test does not check the results of the queries, however, it expects
the resolver to correctly handle those and do not assert.
(cherry picked from commit e88271f2e584010157b068cc998dd76451273562)
(cherry picked from commit bb92832fb6ae899bee7206c2d8966258461c2f71)
Stabilize timing in the cname_dname_negcache test
The #5946 reproducer relied on ans2 answering the negative DNAME/CNAME
query a fixed second after receiving it, racing that delay against the
resolver's per-query timeout. When the timeout fired first -- which
happens under load, most notably under ThreadSanitizer, where named is
slowed but ans2 (wall-clock) is not -- the resolver dropped the late
answer, never processed the negative response, and the watched SOA never
appeared, so the test timed out. This made it flaky on the
security-bind-9.20 CI. Merely shortening the fixed delay would trade
that for the opposite, worse failure: the negative answer arriving
before the positive one is cached, silently not exercising the bug.
Release the negative answer based on the resolver's progress instead of
a wall-clock deadline: hold it until ans2 has sent the positive answer
(a shared event), then wait a short settle for the resolver to cache it.
Both queries traverse the same delegation, so any latency reaching ans2
shifts the positive send and the negative fetch's deadline together and
cancels out; only the settle, kept well under MINIMUM_QUERY_TIMEOUT
(301 ms), has to fit inside the per-query timeout.
Verified that the stabilized test still triggers the
INSIST(namereln == dns_namereln_subdomain) assertion when the resolver
fix is reverted.
Assisted-by: Claude:claude-opus-4-8
(cherry picked from commit 738456d91564526e6f15c3858b4c809cd6749e1e)
(cherry picked from commit 0c20ee4e8e68999ca617434cde65dd3808f57d8c)
Split cname_dname_negcache into per-scenario modules
The DNAME and CNAME scenarios shared a single module, hence a single
module-scoped ns3 (the framework sets servers up per module, not per
test function). test_dname_negcache cached foo.test. DNAME bar.test.;
when test_cname_negcache ran next against the same resolver,
cname.foo.test. was DNAME-mapped to cname.bar.test., so the resolver
never queried ans2 for the self-referential CNAME and that half of the
bug was never exercised. The hardcoded, unanchored "foo.test." watcher
still matched test_dname's leftover SOA, so test_cname passed without
testing anything -- the CNAME assertion had no coverage.
Give each scenario its own module so each gets a fresh server set, and
anchor the watcher to the queried name so a test cannot pass on an
unrelated record.
With the resolver fix reverted, each module now independently triggers
its own assertion:
DNAME query.c INSIST(namereln == dns_namereln_subdomain)
CNAME query.c INSIST(qctx->rdataset == NULL || qctx->qtype == dname)
Assisted-by: Claude:claude-opus-4-8
(cherry picked from commit 3ef0b8d04a1653407cfb9ee88772ae18689b1318)
(cherry picked from commit 887124315f03a006c4dc76e48ae3d0d8aac3c407)
---
.../system/cname_dname_negcache/ans2/ans.py | 148 ++++++++++++++++++
.../system/cname_dname_negcache/common.py | 46 ++++++
.../cname_dname_negcache/ns1/bar.test.db | 5 +
.../cname_dname_negcache/ns1/named.conf.j2 | 24 +++
.../system/cname_dname_negcache/ns1/root.db | 6 +
.../system/cname_dname_negcache/ns1/test.db | 8 +
.../cname_dname_negcache/ns3/named.conf.j2 | 11 ++
.../tests_cname_negcache.py | 16 ++
.../tests_dname_negcache.py | 16 ++
9 files changed, 280 insertions(+)
create mode 100644 bin/tests/system/cname_dname_negcache/ans2/ans.py
create mode 100644 bin/tests/system/cname_dname_negcache/common.py
create mode 100644 bin/tests/system/cname_dname_negcache/ns1/bar.test.db
create mode 100644 bin/tests/system/cname_dname_negcache/ns1/named.conf.j2
create mode 100644 bin/tests/system/cname_dname_negcache/ns1/root.db
create mode 100644 bin/tests/system/cname_dname_negcache/ns1/test.db
create mode 100644 bin/tests/system/cname_dname_negcache/ns3/named.conf.j2
create mode 100644 bin/tests/system/cname_dname_negcache/tests_cname_negcache.py
create mode 100644 bin/tests/system/cname_dname_negcache/tests_dname_negcache.py
diff --git a/bin/tests/system/cname_dname_negcache/ans2/ans.py b/bin/tests/system/cname_dname_negcache/ans2/ans.py
new file mode 100644
index 0000000000..392fe1e088
--- /dev/null
+++ b/bin/tests/system/cname_dname_negcache/ans2/ans.py
@@ -0,0 +1,148 @@
+"""
+Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+
+SPDX-License-Identifier: MPL-2.0
+
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, you can obtain one at https://mozilla.org/MPL/2.0/.
+
+See the COPYRIGHT file distributed with this work for additional
+information regarding copyright ownership.
+"""
+
+from collections.abc import AsyncGenerator
+
+import asyncio
+
+from dns import name, rcode, rdataclass, rdatatype, rrset
+
+from isctest.asyncserver import (
+ AsyncDnsServer,
+ DnsResponseSend,
+ QnameQtypeHandler,
+ QueryContext,
+ StaticResponseHandler,
+)
+
+# The attack relies on the resolver caching the positive CNAME/DNAME answer
+# *before* it processes the negative answer for the same name. The negative
+# answer must therefore be held back until the positive one has been sent, but
+# released again while the negative fetch is still waiting for it.
+#
+# Releasing it at a fixed wall-clock delay (the original approach) is racy: the
+# delay must be larger than the time it takes the resolver to cache the
+# positive answer, yet smaller than the resolver's per-query timeout. Under
+# load -- most notably ThreadSanitizer, which slows down `named` but not this
+# (wall-clock) server -- those bounds can be violated in either direction,
+# making the test either time out (#5946 CI failures) or, worse, silently stop
+# exercising the bug.
+#
+# Instead, gate the negative answer on an event set right after the positive
+# answer is sent. Both queries traverse the same delegation, so any latency in
+# reaching this server shifts the positive send and the negative fetch's
+# deadline together and cancels out; only the small settle below has to fit
+# inside the per-query timeout.
+#
+# _SETTLE must be longer than the few milliseconds the resolver needs to cache
+# the positive answer, and shorter than MINIMUM_QUERY_TIMEOUT (301 ms in
+# lib/dns/resolver.c) so the in-flight negative fetch has not given up yet.
+_SETTLE = 0.1
+
+_dname_positive_sent = asyncio.Event()
+_cname_positive_sent = asyncio.Event()
+
+
+async def _hold_until_positive_cached(positive_sent: asyncio.Event) -> None:
+ await positive_sent.wait()
+ await asyncio.sleep(_SETTLE)
+
+
+def build_rrset(
+ qname: name.Name | str,
+ rtype: rdatatype.RdataType,
+ rdata: str,
+ ttl: int = 300,
+) -> rrset.RRset:
+ return rrset.from_text(qname, ttl, rdataclass.IN, rtype, rdata)
+
+
+class FooTestNsHandler(QnameQtypeHandler, StaticResponseHandler):
+ qnames = ["foo.test."]
+ qtypes = [rdatatype.NS]
+ answer = [build_rrset("foo.test.", rdatatype.NS, "ns.foo.test.")]
+ additional = [build_rrset("ns.foo.test.", rdatatype.A, "10.53.0.2")]
+
+
+class DelayedDnameNegHandler(QnameQtypeHandler, StaticResponseHandler):
+ qnames = ["foo.test."]
+ qtypes = [rdatatype.DNAME]
+ authority = [
+ build_rrset(
+ "foo.test.",
+ rdatatype.SOA,
+ "ns.test. op.ns.test. 2081509183 86400 3600 3600000 300",
+ )
+ ]
+
+ async def get_responses(
+ self, qctx: QueryContext
+ ) -> AsyncGenerator[DnsResponseSend, None]:
+ await _hold_until_positive_cached(_dname_positive_sent)
+ async for response in super().get_responses(qctx):
+ yield response
+
+
+class DnamePosHandler(QnameQtypeHandler, StaticResponseHandler):
+ qnames = ["a.foo.test."]
+ qtypes = [rdatatype.A]
+ answer = [
+ build_rrset("foo.test.", rdatatype.DNAME, "bar.test."),
+ build_rrset("a.foo.test.", rdatatype.CNAME, "a.bar.test."),
+ ]
+
+ async def get_responses(
+ self, qctx: QueryContext
+ ) -> AsyncGenerator[DnsResponseSend, None]:
+ async for response in super().get_responses(qctx):
+ yield response
+ _dname_positive_sent.set()
+
+
+class CnameHandler(QnameQtypeHandler):
+ qnames = ["cname.foo.test."]
+ qtypes = [rdatatype.CNAME, rdatatype.A]
+ answer = [build_rrset("cname.foo.test.", rdatatype.CNAME, "cname.foo.test.")]
+ authority = [
+ build_rrset(
+ "cname.foo.test.",
+ rdatatype.SOA,
+ "ns.test. op.ns.test. 2081509183 86400 3600 3600000 300",
+ )
+ ]
+
+ async def get_responses(
+ self, qctx: QueryContext
+ ) -> AsyncGenerator[DnsResponseSend, None]:
+ if qctx.qtype == rdatatype.CNAME:
+ await _hold_until_positive_cached(_cname_positive_sent)
+ qctx.prepare_new_response(with_zone_data=False)
+ qctx.response.authority.extend(self.authority)
+ yield DnsResponseSend(qctx.response, authoritative=True)
+ else:
+ qctx.prepare_new_response(with_zone_data=False)
+ qctx.response.answer.extend(self.answer)
+ yield DnsResponseSend(qctx.response, authoritative=True)
+ _cname_positive_sent.set()
+
+
+def main() -> None:
+ server = AsyncDnsServer(default_aa=True, default_rcode=rcode.NOERROR)
+ server.install_response_handlers(
+ FooTestNsHandler(), DelayedDnameNegHandler(), DnamePosHandler(), CnameHandler()
+ )
+ server.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/bin/tests/system/cname_dname_negcache/common.py b/bin/tests/system/cname_dname_negcache/common.py
new file mode 100644
index 0000000000..397cfdfa3d
--- /dev/null
+++ b/bin/tests/system/cname_dname_negcache/common.py
@@ -0,0 +1,46 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+from os import environ
+from re import compile as Re
+from re import escape
+from socket import AF_INET, SOCK_DGRAM, socket
+
+import isctest
+
+
+def run_attack(ns, name1, type1, name2, type2):
+ msg1 = isctest.query.create(name1, type1, cd=True)
+ msg2 = isctest.query.create(name2, type2, cd=True)
+ port = int(environ["PORT"])
+
+ with socket(AF_INET, SOCK_DGRAM) as sock:
+ # The order the requests go out doesn't matter. What is important is
+ # that the first query starts recursion before the second query returns
+ # the answer, and the second query returns the answer before the first
+ # query returns the answer. (So, when the NOERROR/NODATA comes back from
+ # the first query, the cache is queried and we get the positive response
+ # cached from the second query attached to the fresp rdataset of the
+ # response of the first query.)
+ # That ordering is enforced by ans2, which holds back the negative
+ # answer to the first query until it has answered the second one (see
+ # ans2/ans.py); the resolver must not crash while reconciling them.
+ sock.sendto(msg1.to_wire(), (ns.ip, port))
+ sock.sendto(msg2.to_wire(), (ns.ip, port))
+
+ # The second query comes back immediately, the resolver caches the DNAME.
+ # The first query comes back shortly after, once ans2 has released the
+ # negative answer, and should not crash the server. Wait for the negative
+ # SOA for this specific name (not just any foo.test. one) so the test cannot
+ # pass on an unrelated record.
+ soa = Re(rf"(?<![\w.]){escape(name1)}.*IN\s+SOA\s+ns\.test\.\s+op\.ns\.test\.")
+ with ns.watch_log_from_start(timeout=15) as watcher:
+ watcher.wait_for_sequence([soa])
diff --git a/bin/tests/system/cname_dname_negcache/ns1/bar.test.db b/bin/tests/system/cname_dname_negcache/ns1/bar.test.db
new file mode 100644
index 0000000000..840b9c3a2c
--- /dev/null
+++ b/bin/tests/system/cname_dname_negcache/ns1/bar.test.db
@@ -0,0 +1,5 @@
+$TTL 300
+bar.test. IN SOA ns.bar.test. hostmaster.bar.test. 1 600 600 1200 600
+bar.test. NS ns.bar.test.
+ns A 10.53.0.1
+a A 10.0.0.1
diff --git a/bin/tests/system/cname_dname_negcache/ns1/named.conf.j2 b/bin/tests/system/cname_dname_negcache/ns1/named.conf.j2
new file mode 100644
index 0000000000..d72dd1181d
--- /dev/null
+++ b/bin/tests/system/cname_dname_negcache/ns1/named.conf.j2
@@ -0,0 +1,24 @@
+options {
+ query-source address @ns.ip@;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { @ns.ip@; };
+ listen-on-v6 { none; };
+ recursion no;
+ dnssec-validation no;
+};
+
+zone "." {
+ type primary;
+ file "root.db";
+};
+
+zone "test." {
+ type primary;
+ file "test.db";
+};
+
+zone "bar.test." {
+ type primary;
+ file "bar.test.db";
+};
diff --git a/bin/tests/system/cname_dname_negcache/ns1/root.db b/bin/tests/system/cname_dname_negcache/ns1/root.db
new file mode 100644
index 0000000000..c456c45b9d
--- /dev/null
+++ b/bin/tests/system/cname_dname_negcache/ns1/root.db
@@ -0,0 +1,6 @@
+$TTL 300
+. IN SOA ns. hostmaster. 1 600 600 1200 600
+. NS a.root-servers.nil.
+a.root-servers.nil. A 10.53.0.1
+test NS ns.test
+ns.test A 10.53.0.1
diff --git a/bin/tests/system/cname_dname_negcache/ns1/test.db b/bin/tests/system/cname_dname_negcache/ns1/test.db
new file mode 100644
index 0000000000..acb68e00f8
--- /dev/null
+++ b/bin/tests/system/cname_dname_negcache/ns1/test.db
@@ -0,0 +1,8 @@
+$TTL 300
+test. IN SOA ns.test. hostmaster.test. 1 600 600 1200 600
+test. NS ns.test.
+ns A 10.53.0.1
+bar NS ns.bar
+ns.bar A 10.53.0.1
+foo NS ns.foo
+ns.foo A 10.53.0.2
diff --git a/bin/tests/system/cname_dname_negcache/ns3/named.conf.j2 b/bin/tests/system/cname_dname_negcache/ns3/named.conf.j2
new file mode 100644
index 0000000000..197d72756b
--- /dev/null
+++ b/bin/tests/system/cname_dname_negcache/ns3/named.conf.j2
@@ -0,0 +1,11 @@
+options {
+ query-source address @ns.ip@;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { @ns.ip@; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation no;
+};
+
+{% include "_common/root.hint.conf" %}
diff --git a/bin/tests/system/cname_dname_negcache/tests_cname_negcache.py b/bin/tests/system/cname_dname_negcache/tests_cname_negcache.py
new file mode 100644
index 0000000000..a546d29109
--- /dev/null
+++ b/bin/tests/system/cname_dname_negcache/tests_cname_negcache.py
@@ -0,0 +1,16 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+from cname_dname_negcache.common import run_attack
+
+
+def test_cname_negcache(ns3):
+ run_attack(ns3, "cname.foo.test.", "CNAME", "cname.foo.test.", "A")
diff --git a/bin/tests/system/cname_dname_negcache/tests_dname_negcache.py b/bin/tests/system/cname_dname_negcache/tests_dname_negcache.py
new file mode 100644
index 0000000000..41a80b4e05
--- /dev/null
+++ b/bin/tests/system/cname_dname_negcache/tests_dname_negcache.py
@@ -0,0 +1,16 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+from cname_dname_negcache.common import run_attack
+
+
+def test_dname_negcache(ns3):
+ run_attack(ns3, "foo.test.", "DNAME", "a.foo.test.", "A")
--
2.55.0

View file

@ -0,0 +1,280 @@
From 3138886f3767c0a6b933fd147c5465472209bac6 Mon Sep 17 00:00:00 2001
From: Colin Vidal <colin@isc.org>
Date: Thu, 18 Jun 2026 18:17:05 +0200
Subject: [PATCH] Do not assert in some CNAME/DNAME queries
Fix a `named` crash because of a fail assertion for certains types of
CNAME and DNAME queries:
- If a client queries for a DNAME and A record to the resolver, and the
authoritative server responds positively to the A query but delay the
DNAME response and respond later negatively;
- If a client queries for a CNAME and A record to the resolver, and the
authoritative server responds positively to the A query but delay the
CNAME response and respond later with a self-referential CNAME.
The first scenario consists of sending two queries: `foo.test./DNAME`
and `a.foo.test./A`. The authoritative server delays the answer for
`foo.test./DNAME` but immediately answers the DNAME record for the
second query: `foo.test. DNAME bar.test.`. The resolver caches it,
follows the DNAME, and resolves `a.bar.test./A`. The authoritative
server eventually answers negatively for `foo.test./DNAME`
(NOERROR/NODATA, with only an SOA in the authority section). The
resolver pulls out the previously cached rdataset (because it has a
higher trust level than the received negative answer), and wrongly (this
is the first bug) sets the result to `DNS_R_DNAME` instead of
`ISC_R_SUCCESS`. The code in `ns/query.c` that handles the resolver
result interprets this as "this is a non-DNAME query and we got a DNAME
rdataset, so follow the chain". It goes into the `query_dname()`
function, which asserts that the qname is a subdomain of the owner name
in the rdataset. That assertion fails because the qname (`foo.test.`) is
exactly equal to the owner name of the DNAME (`foo.test.`), rather than
being a subdomain of it. `DNS_R_DNAME` must only be set when the qtype
is something other than DNAME and the resolver has obtained a DNAME that
needs to be followed.
The second scenario consists of sending two queries:
`cname.foo.test./CNAME` and `cname.foo.test./A`. The authoritative
server delays the answer for `cname.foo.test./CNAME` but immediately
answers the CNAME record for the second query: `cname.foo.test. CNAME
cname.foo.test.`. Note that the CNAME is self-referential. The resolver
caches it and sets the result code to `DNS_R_CNAME`. Then `ns/query.c`
interprets this as "this is a non-CNAME query and we got a CNAME
rdataset, so follow the chain" (which is correct in this case; however,
because the CNAME rdataset is self-referential, the resolver responds
with SERVFAIL, which is expected). The authoritative server eventually
answers negatively for `cname.foo.test./CNAME`. The resolver then pulls
out the previously cached CNAME rdataset (obtained from the A answer,
even though it was self-referential, the resolver cached it) and wrongly
sets the result to `DNS_R_CNAME` (this is the second bug). As noted
above, `ns/query.c` interprets this as "this is a non-CNAME query and we
got a CNAME rdataset, so follow the chain". The internals here are
slightly more subtle: it first goes into `query_cname()` and sets the
CNAME rdataset in the message answer section, then restarts the query to
follow the CNAME. The restart retrieves the CNAME rdataset from the
cache directly (without going to the resolver), and this time the query
context result is `ISC_R_SUCCESS` (since it was found) and
`qctx->rdataset` points to the same CNAME again (as it is
self-referential), so it goes directly into the
`query_prepresponse()/query_respond()` flow, which attempts to add the
rdataset to the message answer again. However, this fails because the
rdataset is already in the message, and the assertion which expects that
operation to succeed fails (due to `qctx->rdataset` being set to `NULL`
when ownership of the rdataset was transferred). `DNS_R_CNAME` must only
be set when the qtype is something other than CNAME and the resolver has
obtained a CNAME that needs to be followed.
In both cases, the correct answer from the resolver should have been
`ISC_R_SUCCESS` (instead of respectively `DNS_R_DNAME` and
`DNS_R_CNAME`) becuase the rdataset that has been looked up was found.
(cherry picked from commit 773d46d58c693047a5945c8fe40512edd0ac214e)
(cherry picked from commit c740c37689f234e21a9b0ef760471ef2cf1133f5)
---
lib/dns/resolver.c | 137 ++++++++++++++++++++-------------------------
1 file changed, 60 insertions(+), 77 deletions(-)
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
index edc9c207e1..1f8b5058d1 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -692,10 +692,10 @@ fctx_destroy(fetchctx_t *fctx, bool exiting);
static void
send_shutdown_events(dns_resolver_t *res);
static isc_result_t
-ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
- dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
- dns_ttl_t maxttl, bool optout, bool secure,
- dns_rdataset_t *ardataset, isc_result_t *eresultp);
+ncache_adderesult(fetchctx_t *fctx, dns_message_t *message, dns_dbnode_t *node,
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl,
+ bool optout, bool secure, dns_rdataset_t *ardataset,
+ isc_result_t *eresultp);
static void
validated(isc_task_t *task, isc_event_t *event);
static void
@@ -5563,6 +5563,46 @@ has_000_label(dns_rdataset_t *nsecset) {
return false;
}
+/*
+ * After a (non-error) negative-cache add, 'rdataset' is bound to whatever
+ * rdataset the cache authoritatively holds for the queried name and type.
+ * Map that to the result code the fetch should report:
+ *
+ * - A negative cache entry (the one we just added, or a pre-existing one):
+ * DNS_R_NCACHENXDOMAIN or DNS_R_NCACHENXRRSET, depending on NXDOMAIN vs
+ * NODATA.
+ *
+ * - A positive rdataset that was already cached at higher trust, which
+ * caused our negative entry to be discarded (e.g. a CNAME or DNAME cached
+ * by a concurrent query): ISC_R_SUCCESS, because that cached positive
+ * answer is what gets returned. Note the specific case for CNAME and
+ * DNAME *if* the query type is not the same as the rdataset type. There
+ * is a chain to follow *only* if the query type doesn't ask for the CNAME
+ * or the DNAME.
+ */
+static isc_result_t
+fctx_setresult(fetchctx_t *fctx, dns_rdataset_t *rdataset) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ if (NEGATIVE(rdataset)) {
+ result = NXDOMAIN(rdataset) ? DNS_R_NCACHENXDOMAIN
+ : DNS_R_NCACHENXRRSET;
+ } else if (result == ISC_R_SUCCESS && rdataset->type != fctx->type) {
+ switch (rdataset->type) {
+ case dns_rdatatype_cname:
+ result = DNS_R_CNAME;
+ break;
+ case dns_rdatatype_dname:
+ result = DNS_R_DNAME;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return result;
+}
+
/*
* The validator has finished.
*/
@@ -5836,8 +5876,7 @@ validated(isc_task_t *task, isc_event_t *event) {
ttl = 0;
}
- result = ncache_adderesult(message, fctx->cache, node, covers,
- now, fctx->res->view->minncachettl,
+ result = ncache_adderesult(fctx, message, node, covers, now,
ttl, vevent->optout, vevent->secure,
ardataset, &eresult);
if (result != ISC_R_SUCCESS) {
@@ -6081,23 +6120,7 @@ answer_response:
*/
INSIST(hevent->rdataset != NULL);
if (dns_rdataset_isassociated(hevent->rdataset)) {
- if (NEGATIVE(hevent->rdataset)) {
- INSIST(eresult == DNS_R_NCACHENXDOMAIN ||
- eresult == DNS_R_NCACHENXRRSET);
- } else if (eresult == ISC_R_SUCCESS &&
- hevent->rdataset->type != fctx->type)
- {
- switch (hevent->rdataset->type) {
- case dns_rdatatype_cname:
- eresult = DNS_R_CNAME;
- break;
- case dns_rdatatype_dname:
- eresult = DNS_R_DNAME;
- break;
- default:
- break;
- }
- }
+ eresult = fctx_setresult(fctx, hevent->rdataset);
}
hevent->result = eresult;
@@ -6747,24 +6770,7 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message,
* event->result.
*/
if (dns_rdataset_isassociated(event->rdataset)) {
- if (NEGATIVE(event->rdataset)) {
- INSIST(eresult ==
- DNS_R_NCACHENXDOMAIN ||
- eresult == DNS_R_NCACHENXRRSET);
- } else if (eresult == ISC_R_SUCCESS &&
- event->rdataset->type != fctx->type)
- {
- switch (event->rdataset->type) {
- case dns_rdatatype_cname:
- eresult = DNS_R_CNAME;
- break;
- case dns_rdatatype_dname:
- eresult = DNS_R_DNAME;
- break;
- default:
- break;
- }
- }
+ eresult = fctx_setresult(fctx, event->rdataset);
}
event->result = eresult;
if (adbp != NULL && *adbp != NULL) {
@@ -6833,12 +6839,14 @@ cache_message(fetchctx_t *fctx, dns_message_t *message,
* eresult.
*/
static isc_result_t
-ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
- dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl,
- dns_ttl_t maxttl, bool optout, bool secure,
- dns_rdataset_t *ardataset, isc_result_t *eresultp) {
+ncache_adderesult(fetchctx_t *fctx, dns_message_t *message, dns_dbnode_t *node,
+ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl,
+ bool optout, bool secure, dns_rdataset_t *ardataset,
+ isc_result_t *eresultp) {
isc_result_t result;
dns_rdataset_t rdataset;
+ dns_db_t *cache = fctx->cache;
+ dns_ttl_t minttl = fctx->res->view->minncachettl;
if (ardataset == NULL) {
dns_rdataset_init(&rdataset);
@@ -6854,37 +6862,13 @@ ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node,
}
if (result == DNS_R_UNCHANGED || result == ISC_R_SUCCESS) {
/*
- * If the cache now contains a negative entry and we
- * care about whether it is DNS_R_NCACHENXDOMAIN or
- * DNS_R_NCACHENXRRSET then extract it.
+ * The cache settled successfully (DNS_R_UNCHANGED means our
+ * negative entry was discarded in favour of existing
+ * higher-trust data). Either way 'ardataset' is now bound to
+ * the rdataset the cache holds for this name and type; derive
+ * the result code from it.
*/
- if (NEGATIVE(ardataset)) {
- /*
- * The cache data is a negative cache entry.
- */
- if (NXDOMAIN(ardataset)) {
- *eresultp = DNS_R_NCACHENXDOMAIN;
- } else {
- *eresultp = DNS_R_NCACHENXRRSET;
- }
- } else {
- /*
- * The attempt to add a negative cache entry
- * was rejected. Set *eresultp to reflect
- * the type of the dataset being returned.
- */
- switch (ardataset->type) {
- case dns_rdatatype_cname:
- *eresultp = DNS_R_CNAME;
- break;
- case dns_rdatatype_dname:
- *eresultp = DNS_R_DNAME;
- break;
- default:
- *eresultp = ISC_R_SUCCESS;
- break;
- }
- }
+ *eresultp = fctx_setresult(fctx, ardataset);
result = ISC_R_SUCCESS;
}
if (ardataset == &rdataset && dns_rdataset_isassociated(ardataset)) {
@@ -7029,8 +7013,7 @@ ncache_message(fetchctx_t *fctx, dns_message_t *message,
ttl = 0;
}
- result = ncache_adderesult(message, fctx->cache, node, covers, now,
- fctx->res->view->minncachettl, ttl, false,
+ result = ncache_adderesult(fctx, message, node, covers, now, ttl, false,
false, ardataset, &eresult);
if (result != ISC_R_SUCCESS) {
goto unlock;
--
2.55.0

View file

@ -0,0 +1,416 @@
From 89e950d215e9922e5af6e3c69b9d6a8750346bb6 Mon Sep 17 00:00:00 2001
From: Alessio Podda <alessio@isc.org>
Date: Fri, 12 Jun 2026 11:16:01 +0200
Subject: [PATCH] Reproducer for #5985 addnoqname mismatch
LLM generated.
(cherry picked from commit 5f4de929b3e4749b6e32c51660be11c47c2514e6)
(cherry picked from commit 0cf010c153518f1f9831e201891ecba8d8ba65e1)
Update reproducer #5985
Update the llm generated reproducer:
- Move server.py into ans/ans1.py
- Remove unncessary named.conf configuration options
- Add comments describing the steps
- Rename system test
(cherry picked from commit fd539807829dd7d2eb76c8b503083f5d84fec6f0)
(cherry picked from commit 6c0e599ea85c0c53a4af09742e64e193da089bb4)
---
.../dnssec_findnoqname_mismatch/ans1/ans.py | 207 ++++++++++++++++++
.../ns2/named.conf.j2 | 33 +++
.../tests_findnoqname_mismatch.py | 126 +++++++++++
3 files changed, 366 insertions(+)
create mode 100644 bin/tests/system/dnssec_findnoqname_mismatch/ans1/ans.py
create mode 100644 bin/tests/system/dnssec_findnoqname_mismatch/ns2/named.conf.j2
create mode 100644 bin/tests/system/dnssec_findnoqname_mismatch/tests_findnoqname_mismatch.py
diff --git a/bin/tests/system/dnssec_findnoqname_mismatch/ans1/ans.py b/bin/tests/system/dnssec_findnoqname_mismatch/ans1/ans.py
new file mode 100644
index 0000000000..b36fc831c8
--- /dev/null
+++ b/bin/tests/system/dnssec_findnoqname_mismatch/ans1/ans.py
@@ -0,0 +1,207 @@
+#!/usr/bin/python3
+
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+
+from collections.abc import AsyncGenerator
+from dataclasses import dataclass
+from datetime import datetime, timedelta, timezone
+from pathlib import Path
+
+import base64
+import json
+
+from cryptography.hazmat.primitives import serialization
+
+import dns.dnssec
+import dns.flags
+import dns.message
+import dns.name
+import dns.rdata
+import dns.rdataclass
+import dns.rcode
+import dns.rdatatype
+import dns.rrset
+
+from isctest.asyncserver import (
+ AsyncDnsServer,
+ DnsResponseSend,
+ QueryContext,
+ ResponseHandler,
+)
+
+TTL = 300
+ZONE = "f217.test."
+CHILD = f"evil.{ZONE}"
+ATTACK = f"www.{CHILD}"
+NSEC_OWNER = f"00000000.{CHILD}"
+NSEC_NEXT = f"zzz.{CHILD}"
+FORGED_A = "192.0.2.217"
+
+
+@dataclass(frozen=True)
+class Key:
+ zone: dns.name.Name
+ private_key: object
+ dnskey: dns.rdata.Rdata
+
+
+def name(text: str) -> dns.name.Name:
+ return dns.name.from_text(text)
+
+
+def load_key() -> Key:
+ path = Path(__file__).resolve().parent / "keys.json"
+ with path.open(encoding="utf-8") as keys_file:
+ raw_key = json.load(keys_file)[ZONE]
+
+ private_key = serialization.load_pem_private_key(
+ raw_key["private_pem"].encode("ascii"),
+ password=None,
+ )
+ dnskey = dns.rdata.from_text(
+ dns.rdataclass.IN, dns.rdatatype.DNSKEY, raw_key["dnskey"]
+ )
+ return Key(name(ZONE), private_key, dnskey)
+
+
+def rrset(owner: str, rdtype: dns.rdatatype.RdataType, *rdatas: str) -> dns.rrset.RRset:
+ return dns.rrset.from_text(owner, TTL, dns.rdataclass.IN, rdtype, *rdatas)
+
+
+def rrset_from_rdata(owner: str, rdata: dns.rdata.Rdata) -> dns.rrset.RRset:
+ return dns.rrset.from_rdata(name(owner), TTL, rdata)
+
+
+def add_signed(
+ section: list[dns.rrset.RRset], covered: dns.rrset.RRset, signer: Key
+) -> None:
+ rrsig = dns.dnssec.sign(
+ covered,
+ signer.private_key,
+ signer.zone,
+ signer.dnskey,
+ lifetime=86400,
+ verify=True,
+ )
+ section.append(covered)
+ section.append(dns.rrset.from_rdata(covered.name, covered.ttl, rrsig))
+
+
+def soa_rrset(zone: str) -> dns.rrset.RRset:
+ return rrset(
+ zone,
+ dns.rdatatype.SOA,
+ f"ns.{ZONE} hostmaster.{ZONE} 1 7200 3600 1209600 300",
+ )
+
+
+def garbage_rrsig(
+ owner: str, covered: dns.rdatatype.RdataType, labels: int, signer: str
+) -> dns.rrset.RRset:
+ now = datetime.now(timezone.utc)
+ inception = (now - timedelta(hours=1)).strftime("%Y%m%d%H%M%S")
+ expiration = (now + timedelta(days=1)).strftime("%Y%m%d%H%M%S")
+ signature = base64.b64encode(bytes(64)).decode("ascii")
+ text = (
+ f"{dns.rdatatype.to_text(covered)} 13 {labels} {TTL} "
+ f"{expiration} {inception} 12345 {signer} {signature}"
+ )
+ rdata = dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.RRSIG, text)
+ return dns.rrset.from_rdata(name(owner), TTL, rdata)
+
+
+def add_ds_denial(response: dns.message.Message, key: Key) -> None:
+ add_signed(response.authority, soa_rrset(ZONE), key)
+ nsec = rrset(CHILD, dns.rdatatype.NSEC, f"ns.{ZONE} NS RRSIG NSEC")
+ add_signed(response.authority, nsec, key)
+
+
+def add_attack_answer(response: dns.message.Message) -> None:
+ """
+ Crafted authoritative response to <q>.evil.f217.hack./A
+
+ ;; ANSWER
+ <q>.evil.f217.hack. 300 IN A 192.0.2.217
+ <q>.evil.f217.hack. 300 IN RRSIG A 13 1 300 <exp> <inc> 12345 evil.f217.hack. <base64 of 64×0x00>
+ ^^^ Labels = 1, qname has 4 labels, wildcard heuristic fires
+
+ ;; AUTHORITY (single owner, three rdatasets in this wire order)
+ 00000000.evil.f217.hack. 300 IN NSEC zzz.evil.f217.hack. A RRSIG NSEC
+ 00000000.evil.f217.hack. 300 IN RRSIG NSEC 13 4 300 <exp> <inc> 12345 evil.f217.hack. <base64 of 64×0x00>
+ 00000000.evil.f217.hack. 300 IN NSEC3 1 0 0 - VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV A RRSIG
+ """
+ # A + RRSIG
+ response.answer.append(rrset(ATTACK, dns.rdatatype.A, FORGED_A))
+ response.answer.append(garbage_rrsig(ATTACK, dns.rdatatype.A, 1, CHILD))
+ # NSEC
+ nsec = rrset(
+ NSEC_OWNER,
+ dns.rdatatype.NSEC,
+ f"{NSEC_NEXT} A RRSIG NSEC",
+ )
+ response.authority.append(nsec)
+ # RRSIG(NSEC)
+ response.authority.append(
+ garbage_rrsig(
+ NSEC_OWNER,
+ dns.rdatatype.NSEC,
+ len(name(NSEC_OWNER).labels) - 1,
+ CHILD,
+ )
+ )
+ # NSEC3
+ nsec3 = rrset(
+ NSEC_OWNER,
+ dns.rdatatype.NSEC3,
+ "1 0 0 - VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV A RRSIG",
+ )
+ response.authority.append(nsec3)
+
+
+class RuntimeCheckHandler(ResponseHandler):
+ def __init__(self, key: Key) -> None:
+ self.key = key
+ self.zone = name(ZONE)
+ self.child = name(CHILD)
+ self.attack = name(ATTACK)
+
+ def match(self, qctx: QueryContext) -> bool:
+ return qctx.qname.is_subdomain(self.zone)
+
+ async def get_responses(
+ self, qctx: QueryContext
+ ) -> AsyncGenerator[DnsResponseSend, None]:
+ qctx.prepare_new_response(with_zone_data=False)
+ qctx.response.flags |= dns.flags.AA
+ qctx.response.set_rcode(dns.rcode.NOERROR)
+
+ if qctx.qname == self.zone and qctx.qtype == dns.rdatatype.DNSKEY:
+ add_signed(
+ qctx.response.answer,
+ rrset_from_rdata(ZONE, self.key.dnskey),
+ self.key,
+ )
+ elif qctx.qname == self.zone and qctx.qtype == dns.rdatatype.SOA:
+ add_signed(qctx.response.answer, soa_rrset(ZONE), self.key)
+ elif qctx.qname == self.child and qctx.qtype == dns.rdatatype.DS:
+ add_ds_denial(qctx.response, self.key)
+ elif qctx.qname == self.child and qctx.qtype == dns.rdatatype.DNSKEY:
+ qctx.response.authority.append(soa_rrset(CHILD))
+ elif qctx.qname == self.attack and qctx.qtype == dns.rdatatype.A:
+ add_attack_answer(qctx.response)
+ else:
+ add_signed(qctx.response.authority, soa_rrset(ZONE), self.key)
+
+ yield DnsResponseSend(qctx.response, authoritative=True)
+
+
+def main() -> None:
+ server = AsyncDnsServer(default_aa=True)
+ server.install_response_handlers(RuntimeCheckHandler(load_key()))
+ server.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/bin/tests/system/dnssec_findnoqname_mismatch/ns2/named.conf.j2 b/bin/tests/system/dnssec_findnoqname_mismatch/ns2/named.conf.j2
new file mode 100644
index 0000000000..f4fbd8a617
--- /dev/null
+++ b/bin/tests/system/dnssec_findnoqname_mismatch/ns2/named.conf.j2
@@ -0,0 +1,33 @@
+// validating resolver
+
+options {
+ query-source address 10.53.0.2;
+ notify-source 10.53.0.2;
+ transfer-source 10.53.0.2;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.2; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation yes;
+};
+
+controls {
+ inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+include "../../_common/rndc.key";
+
+zone "." {
+ type hint;
+ file "../../_common/root.hint";
+};
+
+zone "f217.test" {
+ type static-stub;
+ server-addresses { 10.53.0.1; };
+};
+
+trust-anchors {
+ f217.test. static-key 257 3 13 "@ZONE_DNSKEY@";
+};
diff --git a/bin/tests/system/dnssec_findnoqname_mismatch/tests_findnoqname_mismatch.py b/bin/tests/system/dnssec_findnoqname_mismatch/tests_findnoqname_mismatch.py
new file mode 100644
index 0000000000..f3e332a360
--- /dev/null
+++ b/bin/tests/system/dnssec_findnoqname_mismatch/tests_findnoqname_mismatch.py
@@ -0,0 +1,126 @@
+#!/usr/bin/python3
+
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+
+from pathlib import Path
+
+import json
+
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import ec
+
+import dns.dnssec
+import dns.name
+import dns.rdataclass
+import dns.rdatatype
+import pytest
+
+import isctest
+import isctest.mark
+
+ZONE = "f217.test."
+CHILD = f"evil.{ZONE}"
+ATTACK = f"www.{CHILD}"
+NSEC_OWNER = f"00000000.{CHILD}"
+FORGED_A = "192.0.2.217"
+AUTH = "10.53.0.1"
+RESOLVER = "10.53.0.2"
+
+pytestmark = [
+ isctest.mark.with_ecdsa_deterministic,
+ pytest.mark.extra_artifacts(
+ [
+ "ans1/ans.run",
+ "ans1/keys.json",
+ ]
+ ),
+]
+
+
+def _make_key():
+ private_key = ec.generate_private_key(ec.SECP256R1())
+ dnskey = dns.dnssec.make_dnskey(
+ private_key.public_key(),
+ algorithm="ECDSAP256SHA256",
+ flags=257,
+ )
+ private_pem = private_key.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=serialization.NoEncryption(),
+ ).decode("ascii")
+ return {
+ "private_pem": private_pem,
+ "dnskey": dnskey.to_text(),
+ }
+
+
+def bootstrap():
+ keys = {ZONE: _make_key()}
+ Path("ans1/keys.json").write_text(json.dumps(keys, indent=2), encoding="ascii")
+ zone_dnskey = "".join(keys[ZONE]["dnskey"].split()[3:])
+ return {"ZONE_DNSKEY": zone_dnskey}
+
+
+def _query(server, qname, qtype):
+ query = isctest.query.create(qname, qtype)
+ return isctest.query.tcp(query, server, attempts=1, timeout=5)
+
+
+def _rrset(response, section, owner, rdtype, covers=None):
+ if covers is None:
+ return response.get_rrset(
+ section, dns.name.from_text(owner), dns.rdataclass.IN, rdtype
+ )
+ return response.get_rrset(
+ section,
+ dns.name.from_text(owner),
+ dns.rdataclass.IN,
+ rdtype,
+ covers=covers,
+ )
+
+
+def _has_a(response, section, owner, address):
+ rrset = _rrset(response, section, owner, dns.rdatatype.A)
+ return rrset is not None and any(rdata.address == address for rdata in rrset)
+
+
+def _check_rrsig(response, section, owner, rdtype, signer, labels=None):
+ rrsig = _rrset(response, section, owner, dns.rdatatype.RRSIG, covers=rdtype)
+ assert rrsig is not None, response.to_text()
+ assert rrsig[0].signer == dns.name.from_text(signer), response.to_text()
+ if labels is not None:
+ assert rrsig[0].labels == labels, response.to_text()
+
+
+def test_malicious_findnoqname_addnoqname_mismatch():
+ response = _query(AUTH, ATTACK, "A")
+ isctest.check.noerror(response)
+ assert _has_a(response, response.answer, ATTACK, FORGED_A), response.to_text()
+ _check_rrsig(response, response.answer, ATTACK, dns.rdatatype.A, CHILD, labels=1)
+
+ # Has NSEC
+ assert _rrset(response, response.authority, NSEC_OWNER, dns.rdatatype.NSEC)
+ _check_rrsig(response, response.authority, NSEC_OWNER, dns.rdatatype.NSEC, CHILD)
+ # Has NSEC3
+ assert _rrset(response, response.authority, NSEC_OWNER, dns.rdatatype.NSEC3)
+ assert (
+ _rrset(
+ response,
+ response.authority,
+ NSEC_OWNER,
+ dns.rdatatype.RRSIG,
+ covers=dns.rdatatype.NSEC3,
+ )
+ is None
+ )
+
+
+def test_resolver_findnoqname_addnoqname_mismatch():
+ # Send one trigger query
+ _query(RESOLVER, ATTACK, "A")
+ response = _query(RESOLVER, ZONE, "SOA")
+ isctest.check.noerror(response)
--
2.55.0

View file

@ -0,0 +1,158 @@
From 895cac04332d85489ddf881b28e18e9956f6e348 Mon Sep 17 00:00:00 2001
From: Evan Hunt <each@isc.org>
Date: Wed, 13 May 2026 20:45:57 -0700
Subject: [PATCH] dns_rdataset_addnoqname() could find unsigned NSEC/NSEC3
The dns_rdatalist addnoqname() implementation searches for the first
NSEC or NSEC3 record in a message, then for the first RRSIG covering
that type in the same message. Previously, if no RRSIG for the type was
found, the function accepted the unsigned record. Now, it will instead
continue searching until an NSEC or NSEC3 that does have a matching
signature is found.
When this function is called from validated() in resolver.c, a
non-success return code is now treated as an error instead of triggering
an assertion failure.
Fixes: isc-projects/bind9#5985
(cherry picked from commit 57cba571ee31311e54d8a11cb38094d439f04e09)
(cherry picked from commit 48f5aa5fb3746d6194edcc57e8792a8b3cc3b454)
---
lib/dns/rbtdb.c | 10 +++++++---
lib/dns/rdatalist.c | 33 ++++++++++++++++-----------------
lib/dns/resolver.c | 4 +++-
lib/ns/query.c | 3 +--
4 files changed, 27 insertions(+), 23 deletions(-)
diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c
index 0b8547950f..c922df557b 100644
--- a/lib/dns/rbtdb.c
+++ b/lib/dns/rbtdb.c
@@ -6946,7 +6946,7 @@ delegating_type(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node,
static isc_result_t
addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
uint32_t maxrrperset, dns_rdataset_t *rdataset) {
- struct noqname *noqname;
+ struct noqname *noqname = NULL;
isc_mem_t *mctx = rbtdb->common.mctx;
dns_name_t name;
dns_rdataset_t neg, negsig;
@@ -6958,7 +6958,9 @@ addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
dns_rdataset_init(&negsig);
result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig);
- RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
noqname = isc_mem_get(mctx, sizeof(*noqname));
dns_name_init(&noqname->name, NULL);
@@ -6984,7 +6986,9 @@ addnoqname(dns_rbtdb_t *rbtdb, rdatasetheader_t *newheader,
cleanup:
dns_rdataset_disassociate(&neg);
dns_rdataset_disassociate(&negsig);
- free_noqname(mctx, &noqname);
+ if (noqname != NULL) {
+ free_noqname(mctx, &noqname);
+ }
return result;
}
diff --git a/lib/dns/rdatalist.c b/lib/dns/rdatalist.c
index 98036f9cb3..2cca8d64be 100644
--- a/lib/dns/rdatalist.c
+++ b/lib/dns/rdatalist.c
@@ -192,6 +192,7 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) {
dns_rdataset_t *neg = NULL;
dns_rdataset_t *negsig = NULL;
dns_rdataset_t *rdset;
+ dns_rdataset_t *sigset;
dns_ttl_t ttl;
REQUIRE(rdataset != NULL);
@@ -199,30 +200,27 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) {
for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL;
rdset = ISC_LIST_NEXT(rdset, link))
{
- if (rdset->rdclass != rdataset->rdclass) {
- continue;
- }
- if (rdset->type == dns_rdatatype_nsec ||
- rdset->type == dns_rdatatype_nsec3)
+ if (rdset->rdclass != rdataset->rdclass ||
+ (rdset->type != dns_rdatatype_nsec &&
+ rdset->type != dns_rdatatype_nsec3))
{
- neg = rdset;
+ continue;
}
- }
- if (neg == NULL) {
- return ISC_R_NOTFOUND;
- }
- for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL;
- rdset = ISC_LIST_NEXT(rdset, link))
- {
- if (rdset->type == dns_rdatatype_rrsig &&
- rdset->covers == neg->type)
+ for (sigset = ISC_LIST_HEAD(name->list); sigset != NULL;
+ sigset = ISC_LIST_NEXT(sigset, link))
{
- negsig = rdset;
+ if (sigset->type == dns_rdatatype_rrsig &&
+ sigset->covers == rdset->type)
+ {
+ neg = rdset;
+ negsig = sigset;
+ break;
+ }
}
}
- if (negsig == NULL) {
+ if (neg == NULL || negsig == NULL) {
return ISC_R_NOTFOUND;
}
/*
@@ -238,6 +236,7 @@ isc__rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) {
rdataset->ttl = neg->ttl = negsig->ttl = ttl;
rdataset->attributes |= DNS_RDATASETATTR_NOQNAME;
rdataset->private6 = name;
+
return ISC_R_SUCCESS;
}
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
index 1f8b5058d1..059ce53a9e 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -5893,7 +5893,9 @@ validated(isc_task_t *task, isc_event_t *event) {
result = dns_rdataset_addnoqname(
vevent->rdataset,
vevent->proofs[DNS_VALIDATOR_NOQNAMEPROOF]);
- RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (result != ISC_R_SUCCESS) {
+ goto noanswer_response;
+ }
INSIST(vevent->sigrdataset != NULL);
vevent->sigrdataset->ttl = vevent->rdataset->ttl;
if (vevent->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) {
diff --git a/lib/ns/query.c b/lib/ns/query.c
index 3bd7daf79c..2a2ba1daba 100644
--- a/lib/ns/query.c
+++ b/lib/ns/query.c
@@ -7953,8 +7953,7 @@ query_addnoqnameproof(query_ctx_t *qctx) {
goto cleanup;
}
- result = dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig);
- RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ CHECK(dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig));
query_addrrset(qctx, &fname, &neg, &negsig, dbuf,
DNS_SECTION_AUTHORITY);
--
2.55.0

View file

@ -0,0 +1,445 @@
From eaa35628f4a201049295a8944f4d28e8a1013199 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ayd=C4=B1n=20Mercan?= <aydin@isc.org>
Date: Wed, 6 May 2026 16:54:57 +0300
Subject: [PATCH] Add system test for out-of-zone nsec dnssec bypass
A malicious zone with out-of-zone NSEC entries can get a DNSSEC
validating resolver's cache to cover the victim zone for non-existence
and prevent nameserver queries without DNSSEC failure.
Test for this case with an `evil.test` zone that tries to cover the
`victim.test` zone.
(cherry picked from commit 654f9773c0af59965c343bdfeb096b3dffe9dd53)
(cherry picked from commit c969ad2c17b43dd999e358bfeb280d3df6fab822)
---
.../system/dnssec_bypass/ns1/named.conf.j2 | 31 ++++
bin/tests/system/dnssec_bypass/ns1/root.db | 19 +++
bin/tests/system/dnssec_bypass/ns1/test.db | 23 +++
.../system/dnssec_bypass/ns2/named.conf.j2 | 26 +++
bin/tests/system/dnssec_bypass/ns2/victim.db | 18 +++
bin/tests/system/dnssec_bypass/ns3/evil.db | 23 +++
.../system/dnssec_bypass/ns3/named.conf.j2 | 26 +++
.../system/dnssec_bypass/ns4/named.conf.j2 | 35 ++++
.../system/dnssec_bypass/tests_bypass.py | 152 ++++++++++++++++++
9 files changed, 353 insertions(+)
create mode 100644 bin/tests/system/dnssec_bypass/ns1/named.conf.j2
create mode 100644 bin/tests/system/dnssec_bypass/ns1/root.db
create mode 100644 bin/tests/system/dnssec_bypass/ns1/test.db
create mode 100644 bin/tests/system/dnssec_bypass/ns2/named.conf.j2
create mode 100644 bin/tests/system/dnssec_bypass/ns2/victim.db
create mode 100644 bin/tests/system/dnssec_bypass/ns3/evil.db
create mode 100644 bin/tests/system/dnssec_bypass/ns3/named.conf.j2
create mode 100644 bin/tests/system/dnssec_bypass/ns4/named.conf.j2
create mode 100644 bin/tests/system/dnssec_bypass/tests_bypass.py
diff --git a/bin/tests/system/dnssec_bypass/ns1/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns1/named.conf.j2
new file mode 100644
index 0000000000..59ced1831a
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns1/named.conf.j2
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.1; };
+ listen-on-v6 { none; };
+ recursion no;
+ dnssec-validation no;
+};
+
+zone "test." {
+ type primary;
+ file "test.db.signed";
+};
+
+zone "." {
+ type primary;
+ file "root.db.signed";
+};
diff --git a/bin/tests/system/dnssec_bypass/ns1/root.db b/bin/tests/system/dnssec_bypass/ns1/root.db
new file mode 100644
index 0000000000..8d98a0456c
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns1/root.db
@@ -0,0 +1,19 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 3600
+. IN SOA a.nil. a.nil. 1 3600 600 86400 300
+. IN NS a.root-servers.nil.
+
+a.root-servers.nil. IN A 10.53.0.1
+
+test. IN NS ns1.test.
+ns1.test. IN A 10.53.0.1
diff --git a/bin/tests/system/dnssec_bypass/ns1/test.db b/bin/tests/system/dnssec_bypass/ns1/test.db
new file mode 100644
index 0000000000..6efcd95e42
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns1/test.db
@@ -0,0 +1,23 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$ORIGIN test.
+$TTL 3600
+
+@ IN SOA a a 1 3600 600 86400 300
+ IN NS ns1.test.
+ns1 IN A 10.53.0.1
+
+evil IN NS ns1.evil
+ns1.evil IN A 10.53.0.3
+
+victim IN NS ns1.victim
+ns1.victim IN A 10.53.0.2
diff --git a/bin/tests/system/dnssec_bypass/ns2/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns2/named.conf.j2
new file mode 100644
index 0000000000..e81cee7cac
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns2/named.conf.j2
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.2; };
+ listen-on-v6 { none; };
+ recursion no;
+ dnssec-validation no;
+};
+
+zone "victim.test." {
+ type primary;
+ file "victim.db.signed";
+};
diff --git a/bin/tests/system/dnssec_bypass/ns2/victim.db b/bin/tests/system/dnssec_bypass/ns2/victim.db
new file mode 100644
index 0000000000..edcc234322
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns2/victim.db
@@ -0,0 +1,18 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$ORIGIN victim.test.
+$TTL 3600
+
+@ IN SOA ns1 hostmaster 1 3600 600 86400 2147483647
+ IN NS ns1
+
+ns1 IN A 10.53.0.2
diff --git a/bin/tests/system/dnssec_bypass/ns3/evil.db b/bin/tests/system/dnssec_bypass/ns3/evil.db
new file mode 100644
index 0000000000..618f9d3e85
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns3/evil.db
@@ -0,0 +1,23 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$ORIGIN evil.test.
+$TTL 300
+
+@ IN SOA ns1 hostmaster 1 3600 600 86400 300
+ IN NS ns1
+; Try to poison the victim zone in a resolver cache.
+; If admitted, the aggressive NSEC cache will accept a range such as
+; [evil.test, b.victim.test) and will cause the victim nameserver to
+; be never queried.
+ IN NSEC b.victim.test. NS SOA RRSIG NSEC DNSKEY
+
+ns1 IN A 10.53.0.3
diff --git a/bin/tests/system/dnssec_bypass/ns3/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns3/named.conf.j2
new file mode 100644
index 0000000000..17d3e18e4e
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns3/named.conf.j2
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.3; };
+ listen-on-v6 { none; };
+ recursion no;
+ dnssec-validation no;
+};
+
+zone "evil.test." {
+ type primary;
+ file "evil.db.signed";
+};
diff --git a/bin/tests/system/dnssec_bypass/ns4/named.conf.j2 b/bin/tests/system/dnssec_bypass/ns4/named.conf.j2
new file mode 100644
index 0000000000..039695d9b7
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/ns4/named.conf.j2
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ query-source address 10.53.0.4;
+ notify-source 10.53.0.4;
+ transfer-source 10.53.0.4;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.4; };
+ listen-on-v6 { none; };
+ allow-transfer { any; };
+ recursion yes;
+ dnssec-validation yes;
+ synth-from-dnssec yes;
+};
+
+trust-anchors {
+ @root.domain@ @root.type@ @root.contents@;
+};
+
+zone "." {
+ type hint;
+ file "../../_common/root.hint";
+};
diff --git a/bin/tests/system/dnssec_bypass/tests_bypass.py b/bin/tests/system/dnssec_bypass/tests_bypass.py
new file mode 100644
index 0000000000..c41bb7e016
--- /dev/null
+++ b/bin/tests/system/dnssec_bypass/tests_bypass.py
@@ -0,0 +1,152 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+from datetime import datetime, timedelta, timezone
+
+import shutil
+
+from cryptography.hazmat.primitives.asymmetric import ec
+
+import dns.dnssec
+import dns.name
+import dns.rdataclass
+import dns.rdataset
+import dns.rdatatype
+import dns.rrset
+import dns.zone
+
+from isctest.run import EnvCmd
+
+import isctest
+
+TTL = 3600
+
+
+def bootstrap():
+ keygen = EnvCmd("KEYGEN", "-q -a ECDSA256")
+ signer = EnvCmd("SIGNER", "-S -g -O full")
+
+ def sign_regular_zone(ns: str, zone: str, database: str) -> isctest.kasp.Key:
+ isctest.log.info(f"{zone}: generate keys")
+ keygen(zone, cwd=ns).out.strip()
+ ksk = keygen(f"-f KSK {zone}", cwd=ns).out.strip()
+
+ isctest.log.info(f"{zone}: sign zone")
+ signer(f"-o {zone} {database}", cwd=ns)
+
+ if ns != "ns1":
+ shutil.copy(f"{ns}/dsset-{zone}", f"ns1/dsset-{zone}")
+ shutil.copy(f"{ns}/{ksk}.key", f"ns1/{ksk}.key")
+
+ return isctest.kasp.Key(ksk, keydir=ns)
+
+ # dnssec-signzone and `dns.dnssec.sign_zone` correctly disregard the invalid
+ # NSEC entries when signing the zone. However, for this test we actualy *want*
+ # to serve invalid yet signed zones. To accomplish this we sign the zone and then
+ # replace the correct entries with the faulty ones accompanied by its RRSIG.
+ #
+ # TODO(aydin): move this to `isctest` to sign broken zones
+ def sign_rogue_zone(ns: str, zone: str, database: str) -> None:
+ # Read zone.
+ origin = dns.name.from_text(zone)
+ data = dns.zone.from_file(f"{ns}/{database}", origin=origin, relativize=False)
+
+ # Get key for signing.
+ isctest.log.info(f"{zone}: generate keys")
+ private_key = ec.generate_private_key(ec.SECP256R1())
+ dnskey = dns.dnssec.make_dnskey(
+ public_key=private_key.public_key(),
+ algorithm=dns.dnssec.Algorithm.ECDSAP256SHA256,
+ flags=257,
+ )
+
+ # Sign zone.
+ isctest.log.info(f"{zone}: sign zone")
+ now = datetime.now(timezone.utc)
+ inception = now - timedelta(hours=1)
+ expiration = now + timedelta(days=30)
+
+ for name, node in data.nodes.items():
+ owner = name.derelativize(origin)
+ rdatasets = list(node.rdatasets)
+
+ for rdataset in rdatasets:
+ rrset = dns.rrset.RRset(owner, rdataset.rdclass, rdataset.rdtype)
+ rrset.update(rdataset)
+
+ rrsig = dns.dnssec.sign(
+ rrset=rrset,
+ private_key=private_key,
+ signer=origin,
+ dnskey=dnskey,
+ inception=inception,
+ expiration=expiration,
+ deterministic=False,
+ )
+
+ rdataset = dns.rdataset.Rdataset(rrset.rdclass, dns.rdatatype.RRSIG)
+ rdataset.add(rrsig, rrset.ttl)
+ node.replace_rdataset(rdataset)
+
+ # Sign DNSKEY RRset.
+ dnskey_rrset = dns.rrset.RRset(origin, dns.rdataclass.IN, dns.rdatatype.DNSKEY)
+ dnskey_rrset.add(dnskey, ttl=TTL)
+
+ apex_node = data.nodes[origin]
+ apex_node.replace_rdataset(dnskey_rrset)
+
+ rrsig = dns.dnssec.sign(
+ rrset=dnskey_rrset,
+ private_key=private_key,
+ signer=origin,
+ dnskey=dnskey,
+ inception=inception,
+ expiration=expiration,
+ deterministic=False,
+ )
+ rdataset = dns.rdataset.Rdataset(rrset.rdclass, dns.rdatatype.RRSIG)
+ rdataset.add(rrsig, dnskey_rrset.ttl)
+ apex_node.replace_rdataset(rdataset)
+
+ # Output zone.
+ data.to_file(f"{ns}/{database}.signed", relativize=False)
+
+ # Output DS.
+ ds = dns.dnssec.make_ds(name=origin, key=dnskey, algorithm="SHA256")
+ with open(f"ns1/dsset-{zone}", "w", encoding="utf-8") as f:
+ f.write(f"{zone} {TTL} IN DS {ds.to_text()}\n")
+
+ sign_rogue_zone("ns3", "evil.test.", "evil.db")
+ sign_regular_zone("ns2", "victim.test.", "victim.db")
+ sign_regular_zone("ns1", "test.", "test.db")
+ root_ksk = sign_regular_zone("ns1", ".", "root.db")
+
+ return {
+ "root": root_ksk.into_ta("static-key"),
+ }
+
+
+def test_out_of_zone_nsec(ns4):
+ isctest.log.info("trying to poison aggressive nsec cache")
+ msg = isctest.query.create("nx.evil.test", "A")
+ res = isctest.query.tcp(msg, ns4.ip)
+ isctest.check.noadflag(res)
+
+ isctest.log.info("query victim from recursive")
+ msg = isctest.query.create("victim.test", "SOA")
+ res = isctest.query.tcp(msg, ns4.ip, attempts=1)
+ isctest.check.noerror(res)
+ isctest.check.adflag(res)
+ isctest.check.rr_count_eq(res.answer, 2)
+
+ isctest.log.info("checking for query history on victim nameserver")
+ with open("ns2/named.run", "r", encoding="utf-8") as f:
+ assert "(victim.test): query 'victim.test/SOA/IN' approved" in f.read()
--
2.55.0

View file

@ -0,0 +1,277 @@
From 72967445f37a01d28b4ecb0e8f907e22fddd5087 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ayd=C4=B1n=20Mercan?= <aydin@isc.org>
Date: Thu, 7 May 2026 18:59:20 +0300
Subject: [PATCH] Reject out-of-zone NSEC next owner names
When verifying DNSSEC records, make sure that a next owner name of
an NSEC record is a subdomain of the signer field.
This follows the specification RFC 4034, section 4.1.1:
Owner names of RRsets for which the given zone is not authoritative
(such as glue records) MUST NOT be listed in the Next Domain Name
unless at least one authoritative RRset exists at the same owner
name.
While the above paragraph is intended for glue records, it also
applies to out-of-zone data.
(cherry picked from commit 4065512d25b71605b9502bb69dfb903776d35aa9)
(cherry picked from commit 058023c66f11d78590d4aa8c4f98946c4c965e21)
change dns_nsec_requiredtypespresent to dns_nsec_is_legal
Change `dns_nsec_requiredtypespresent` to `dns_nsec_is_legal` as a
function for checking multiple NSEC validity rules.
Currently we now additionally check for out-of-zone NSEC entries.
(cherry picked from commit be2a6a497312469890b552907d039d2de0b44ccc)
(cherry picked from commit f751e19a30d107f04c2f644aff9f8dab8fed03ab)
---
lib/dns/dnssec.c | 13 ++++++++++
lib/dns/include/dns/dnssec.h | 6 +++++
lib/dns/include/dns/nsec.h | 18 ++++++++++----
lib/dns/nsec.c | 17 ++++++++++---
lib/dns/resolver.c | 48 ++++++++++++++++++++++++++++++++++--
lib/ns/query.c | 6 ++---
6 files changed, 94 insertions(+), 14 deletions(-)
diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c
index 9b9b1f2bb2..5acaea9ecb 100644
--- a/lib/dns/dnssec.c
+++ b/lib/dns/dnssec.c
@@ -357,8 +357,10 @@ isc_result_t
dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
bool ignoretime, unsigned int maxbits, isc_mem_t *mctx,
dns_rdata_t *sigrdata, dns_name_t *wild) {
+ dns_rdata_nsec_t nsec;
dns_rdata_rrsig_t sig;
dns_fixedname_t fnewname;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
isc_region_t r;
isc_buffer_t envbuf;
dns_rdata_t *rdatas;
@@ -464,6 +466,17 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
}
break;
}
+ /*
+ * Check for out of zone NSEC entries.
+ */
+ if (set->type == dns_rdatatype_nsec) {
+ RETERR(dns_rdataset_first(set));
+ dns_rdataset_current(set, &rdata);
+ RETERR(dns_rdata_tostruct(&rdata, &nsec, NULL));
+ if (!dns_name_issubdomain(&nsec.next, &sig.signer)) {
+ return DNS_R_NOVALIDNSEC;
+ }
+ }
again:
ret = dst_context_create(key, mctx, DNS_LOGCATEGORY_DNSSEC, false,
diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h
index cb8fd9dc20..2be11b9144 100644
--- a/lib/dns/include/dns/dnssec.h
+++ b/lib/dns/include/dns/dnssec.h
@@ -151,6 +151,9 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
* this record, as this requires a resolver or database.
* If 'ignoretime' is true, temporal validity will not be checked.
*
+ * If 'set' is of type NSEC, this function also verifies that the
+ * Next Name is a subdomain of the Signer's Name from 'sigrdata'.
+ *
* 'maxbits' specifies the maximum number of rsa exponent bits accepted.
*
* Requires:
@@ -173,6 +176,9 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key,
*\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data (either
* it is not a zone key or its flags prevent
* authentication)
+ *
+ *\li #DNS_R_NOVALIDNSEC - the NSEC rdata is not valid
+ *\li #DNS_R_KEYUNAUTHORIZED - the key cannot sign this data
*\li DST_R_*
*/
diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h
index 50df8e45f1..1e71bf14e8 100644
--- a/lib/dns/include/dns/nsec.h
+++ b/lib/dns/include/dns/nsec.h
@@ -119,13 +119,21 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
*/
bool
-dns_nsec_requiredtypespresent(dns_rdataset_t *rdataset);
-/*
- * Return true if all the NSEC records in rdataset have both
- * NSEC and RRSIG present.
+dns_nsec_is_legal(dns_rdataset_t *rdataset, const dns_name_t *name);
+/**<
+ * \brief
+ * Validates a rdataset of type NSEC.
*
- * Requires:
+ * This functions checks for the following in the given rdataset:
+ * \li All NSEC records have both NSEC and RRSIG present
+ * \li All NSEC entries are under the `name`
+ *
+ * \par Requires:
* \li rdataset to be a NSEC rdataset.
+ * \li `name` is a valid dns_name_t
+ *
+ * \retval true if all the checks pass
+ * \retval false otherwise
*/
ISC_LANG_ENDDECLS
diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c
index 80ee8d7d58..5abcce5f7f 100644
--- a/lib/dns/nsec.c
+++ b/lib/dns/nsec.c
@@ -21,6 +21,7 @@
#include <isc/util.h>
#include <dns/db.h>
+#include <dns/name.h>
#include <dns/nsec.h>
#include <dns/rdata.h>
#include <dns/rdatalist.h>
@@ -497,8 +498,9 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
}
bool
-dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) {
- dns_rdataset_t rdataset;
+dns_nsec_is_legal(dns_rdataset_t *nsecset, const dns_name_t *name) {
+ dns_rdataset_t rdataset = DNS_RDATASET_INIT;
+ dns_rdata_nsec_t nsec;
isc_result_t result;
bool found = false;
@@ -513,12 +515,19 @@ dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) {
{
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_current(&rdataset, &rdata);
- if (!dns_nsec_typepresent(&rdata, dns_rdatatype_nsec) ||
- !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig))
+
+ /* must never fail */
+ result = dns_rdata_tostruct(&rdata, &nsec, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+
+ if (!dns_name_issubdomain(&nsec.next, name) ||
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig) ||
+ !dns_nsec_typepresent(&rdata, dns_rdatatype_nsec))
{
dns_rdataset_disassociate(&rdataset);
return false;
}
+
found = true;
}
dns_rdataset_disassociate(&rdataset);
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
index 059ce53a9e..eac67f7a19 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -65,7 +65,9 @@
#include <dns/rootns.h>
#include <dns/stats.h>
#include <dns/tsig.h>
+#include <dns/types.h>
#include <dns/validator.h>
+#include <dns/view.h>
#include <dns/zone.h>
/* Detailed logging of fctx attach/detach */
@@ -5603,6 +5605,36 @@ fctx_setresult(fetchctx_t *fctx, dns_rdataset_t *rdataset) {
return result;
}
+static bool
+get_and_check_signer_name(dns_name_t *signer, dns_rdataset_t *sigrdataset) {
+ dns_rdata_rrsig_t rrsig;
+ isc_result_t result;
+ dns_rdata_t rdata;
+
+ if (dns_rdataset_first(sigrdataset) != ISC_R_SUCCESS) {
+ return false;
+ }
+
+ rdata = (dns_rdata_t)DNS_RDATA_INIT;
+ dns_rdataset_current(sigrdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_name_copy(&rrsig.signer, signer);
+
+ while (dns_rdataset_next(sigrdataset) == ISC_R_SUCCESS) {
+ rdata = (dns_rdata_t)DNS_RDATA_INIT;
+ dns_rdataset_current(sigrdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ INSIST(result == ISC_R_SUCCESS);
+
+ if (!dns_name_equal(signer, &rrsig.signer)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
/*
* The validator has finished.
*/
@@ -5633,6 +5665,8 @@ validated(isc_task_t *task, isc_event_t *event) {
dns_fixedname_t fwild;
dns_name_t *wild = NULL;
dns_message_t *message = NULL;
+ dns_fixedname_t fsigner;
+ dns_name_t *signer = NULL;
UNUSED(task); /* for now */
@@ -6021,10 +6055,20 @@ answer_response:
}
/*
- * Don't cache NSEC if missing NSEC or RRSIG types.
+ * Don't cache if all the RRSIGs don't have the same
+ * signer.
+ */
+ signer = dns_fixedname_initname(&fsigner);
+ if (!get_and_check_signer_name(signer, sigrdataset)) {
+ continue;
+ }
+
+ /*
+ * Don't cache NSEC if missing NSEC or RRSIG
+ * types.
*/
if (rdataset->type == dns_rdatatype_nsec &&
- !dns_nsec_requiredtypespresent(rdataset))
+ !dns_nsec_is_legal(rdataset, signer))
{
continue;
}
diff --git a/lib/ns/query.c b/lib/ns/query.c
index 2a2ba1daba..8bbcd7ff30 100644
--- a/lib/ns/query.c
+++ b/lib/ns/query.c
@@ -10370,10 +10370,10 @@ query_coveringnsec(query_ctx_t *qctx) {
}
/*
- * If NSEC or RRSIG are missing from the type map
- * reject the NSEC RRset.
+ * Check that the NSEC entry is legal.
+ * (NSEC + RRSIG present and the entry isn't out-of-zone)
*/
- if (!dns_nsec_requiredtypespresent(qctx->rdataset)) {
+ if (!dns_nsec_is_legal(qctx->rdataset, signer)) {
goto cleanup;
}
--
2.55.0

View file

@ -0,0 +1,76 @@
From 12f50726b6bd8f6b3ed6709695e0f6893bc865c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
Date: Tue, 16 Sep 2025 11:46:03 +0200
Subject: [PATCH] Fix expectations on idna system test
IDNA tests always redirect output into the file. That means its
behaviour has changed and is now processing IDN input by default and
just disables IDN output by default.
New behaviour when redirected is the same as +idnin +noidnout, but does
not fail hard on input errors.
---
bin/tests/system/idna/tests.sh | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/bin/tests/system/idna/tests.sh b/bin/tests/system/idna/tests.sh
index 398b7e1..37f02b1 100644
--- a/bin/tests/system/idna/tests.sh
+++ b/bin/tests/system/idna/tests.sh
@@ -194,7 +194,7 @@ idna_enabled_test() {
# Note that ASCII characters are converted to lower-case.
text="Checking valid non-ASCII label"
- idna_test "$text" "" "München" "M\195\188nchen."
+ idna_test "$text" "" "München" "xn--mnchen-3ya."
idna_test "$text" "+noidnin +noidnout" "München" "M\195\188nchen."
idna_test "$text" "+noidnin +idnout" "München" "M\195\188nchen."
idna_test "$text" "+idnin +noidnout" "München" "xn--mnchen-3ya."
@@ -218,7 +218,7 @@ idna_enabled_test() {
# for the valid U-label.
text="Checking that non-transitional IDNA processing is used"
- idna_test "$text" "" "faß.de" "fa\195\159.de."
+ idna_test "$text" "" "faß.de" "xn--fa-hia.de."
idna_test "$text" "+noidnin +noidnout" "faß.de" "fa\195\159.de."
idna_test "$text" "+noidnin +idnout" "faß.de" "fa\195\159.de."
idna_test "$text" "+idnin +noidnout" "faß.de" "xn--fa-hia.de."
@@ -228,7 +228,7 @@ idna_enabled_test() {
# onto the Greek sigma character ("σ") in IDNA2003.
text="Second check that non-transitional IDNA processing is used"
- idna_test "$text" "" "βόλος.com" "\206\178\207\140\206\187\206\191\207\130.com."
+ idna_test "$text" "" "βόλος.com" "xn--nxasmm1c.com."
idna_test "$text" "+noidnin +noidnout" "βόλος.com" "\206\178\207\140\206\187\206\191\207\130.com."
idna_test "$text" "+noidnin +idnout" "βόλος.com" "\206\178\207\140\206\187\206\191\207\130.com."
idna_test "$text" "+idnin +noidnout" "βόλος.com" "xn--nxasmm1c.com."
@@ -288,7 +288,7 @@ idna_enabled_test() {
idna_test "$text" "" "xn--xx" "xn--xx."
idna_test "$text" "+noidnin +noidnout" "xn--xx" "xn--xx."
idna_fail "$text" "+noidnin +idnout" "xn--xx"
- idna_fail "$text" "+idnin +noidnout" "xn--xx"
+ idna_test "$text" "+idnin +noidnout" "xn--xx" "xn--xx."
idna_fail "$text" "+idnin +idnout" "xn--xx"
# Fake A-label - the string does not translate to anything.
@@ -297,7 +297,7 @@ idna_enabled_test() {
idna_test "$text" "" "xn--ahahah" "xn--ahahah."
idna_test "$text" "+noidnin +noidnout" "xn--ahahah" "xn--ahahah."
idna_fail "$text" "+noidnin +idnout" "xn--ahahah"
- idna_fail "$text" "+idnin +noidnout" "xn--ahahah"
+ idna_test "$text" "+idnin +noidnout" "xn--ahahah" "xn--ahahah."
idna_fail "$text" "+idnin +idnout" "xn--ahahah"
# Too long a label. The punycode string is too long (at 64 characters).
@@ -324,7 +324,7 @@ idna_enabled_test() {
# The +[no]idnout options should not have any effect on the test.
text="Checking invalid input U-label"
- idna_test "$text" "" "√.com" "\226\136\154.com."
+ idna_test "$text" "" "√.com" "xn--19g.com."
idna_test "$text" "+noidnin +noidnout" "√.com" "\226\136\154.com."
idna_test "$text" "+noidnin +idnout" "√.com" "\226\136\154.com."
idna_test "$text" "+idnin +noidnout" "√.com" "xn--19g.com."
--
2.51.0

View file

@ -0,0 +1,98 @@
From fcc50604359a05e24003f3ff51c3812d8f307814 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
Date: Wed, 6 Nov 2024 21:29:47 +0100
Subject: [PATCH] Allow always IDN input in dig
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Even when stdout is non-interactive terminal, allow unicode characters
to be encoded into ACE form. Still disable IDN output, but unless
+noidnin or IDN_DISABLE=1 env is detected, consider input as locale
defined name.
Provides more isolated change, which issue #3527 introduced similar
behavior into 9.19 with more changes.
Ignore input IDN errors when stdout is not terminal
Attempt to prevent visible regressions when enabling IDN on input
always. Instead of new hard failures preventing IDN decoding of input
name just use original input.
Should make the change backward compatible. When on interactive terminal
behave the same way as before and emit hard errors. Become more
forgiving in scripts where stdout leads to script. Decoding output is
not enabled there and if input decoding fails, just use input as it was.
Change dig manual +idnin
Note in manual IDN input is always enabled. But it silently ignores
errors when stdout is not a terminal to prevent regressions.
Signed-off-by: Petr Menšík <pemensik@redhat.com>
---
bin/dig/dig.rst | 5 ++---
bin/dig/dighost.c | 16 ++++++++++++----
2 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/bin/dig/dig.rst b/bin/dig/dig.rst
index 88b0a40307..e2bf3764d3 100644
--- a/bin/dig/dig.rst
+++ b/bin/dig/dig.rst
@@ -453,9 +453,8 @@ abbreviation is unambiguous; for example, :option:`+cd` is equivalent to
This option processes [or does not process] IDN domain names on input. This requires
``IDN SUPPORT`` to have been enabled at compile time.
- The default is to process IDN input when standard output is a tty.
- The IDN processing on input is disabled when :program:`dig` output is redirected
- to files, pipes, and other non-tty file descriptors.
+ The default is to process IDN input. The input IDN processing errors are ignored
+ when :program:`dig` output is redirected to files, pipes, and other non-tty file descriptors.
.. option:: +idnout, +noidnout
diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c
index 0f8ac1335c..1307346192 100644
--- a/bin/dig/dighost.c
+++ b/bin/dig/dighost.c
@@ -604,7 +604,7 @@ dig_lookup_t *
make_empty_lookup(void) {
dig_lookup_t *looknew;
#ifdef HAVE_LIBIDN2
- bool idn_allowed = isatty(1) ? (getenv("IDN_DISABLE") == NULL) : false;
+ bool idn_allowed = (getenv("IDN_DISABLE") == NULL);
#endif /* HAVE_LIBIDN2 */
debug("make_empty_lookup()");
@@ -623,7 +623,7 @@ make_empty_lookup(void) {
.badcookie = true,
#ifdef HAVE_LIBIDN2
.idnin = idn_allowed,
- .idnout = idn_allowed,
+ .idnout = isatty(1) && idn_allowed,
#endif /* HAVE_LIBIDN2 */
.udpsize = -1,
.edns = -1,
@@ -4871,8 +4871,16 @@ idn_locale_to_ace(const char *src, char *dst, size_t dstlen) {
res = idn2_to_ascii_lz(src, &ascii_src, IDN2_TRANSITIONAL);
}
if (res != IDN2_OK) {
- fatal("'%s' is not a legal IDNA2008 name (%s), use +noidnin",
- src, idn2_strerror(res));
+ if (isatty(1)) {
+ fatal("'%s' is not a legal IDNA2008 name (%s), use +noidnin",
+ src, idn2_strerror(res));
+ } else {
+ /* In case of non-terminal output silently ignore errors
+ * in IDN input decoding. */
+ (void)strlcpy(dst, src, dstlen);
+ resetlocale(LC_ALL);
+ return;
+ }
}
/*
--
2.50.1

View file

@ -0,0 +1,54 @@
From 3f686891729c7d39d879e8b5bb1aa17d874d265d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
Date: Thu, 19 Jun 2025 19:51:43 +0200
Subject: [PATCH] Limit number of additional records fetched
Limit number of started fetches for additional zone instead of doing
none. Keep limit of NS filled with additional records, but present at
least some if possible.
Might help broken implementations relying on receiving addresses in the
response for NS query in authoritative zone.
---
lib/dns/rdataset.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/lib/dns/rdataset.c b/lib/dns/rdataset.c
index 532e49a..bfa8e37 100644
--- a/lib/dns/rdataset.c
+++ b/lib/dns/rdataset.c
@@ -581,6 +581,7 @@ dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
size_t limit) {
dns_rdata_t rdata = DNS_RDATA_INIT;
isc_result_t result;
+ size_t n = 0;
/*
* For each rdata in rdataset, call 'add' for each name and type in the
@@ -590,10 +591,6 @@ dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
REQUIRE(DNS_RDATASET_VALID(rdataset));
REQUIRE((rdataset->attributes & DNS_RDATASETATTR_QUESTION) == 0);
- if (limit != 0 && dns_rdataset_count(rdataset) > limit) {
- return DNS_R_TOOMANYRECORDS;
- }
-
result = dns_rdataset_first(rdataset);
if (result != ISC_R_SUCCESS) {
return result;
@@ -603,7 +600,11 @@ dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
dns_rdataset_current(rdataset, &rdata);
result = dns_rdata_additionaldata(&rdata, owner_name, add, arg);
if (result == ISC_R_SUCCESS) {
- result = dns_rdataset_next(rdataset);
+ if (limit != 0 && ++n >= limit) {
+ result = DNS_R_TOOMANYRECORDS;
+ } else {
+ result = dns_rdataset_next(rdataset);
+ }
}
dns_rdata_reset(&rdata);
} while (result == ISC_R_SUCCESS);
--
2.50.1

View file

@ -0,0 +1,897 @@
From 5bd1369eb7781ad2b349b99f783a7ed07fb7d6ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
Date: Thu, 13 Feb 2025 13:20:28 +0100
Subject: [PATCH] Backport OpenSSL 3 provider support
Use gist of 451edf324281d30fbbe5669223dcea331670847c and
5fd6cfc625aa84005618236f4cd699c07367a3dc upstream commits, but do not do
significant rebase together. Move engine loading of EVP_PKEY from label to
openssl_link and copy provider variant from newer.
Remove legacy RSA calls from _fromlabel to separate engine handling
code. Make rsa_check accepting EVP_PKEY pair only and use conditional
compilation to verify them. Move checking of max exponent bits to
rsa_check too, because it is done from all usages anyway.
Use rsa_check_legacy in places where bit checking is not necessary.
Fix keyfromlabel to not use engine parameter for provider keys
- Rework key checks to not require 'engine' tag, private key
is valid with 'label' tag alone
- Fix _fromlabel() functions to work with engine == NULL
- Update dst__openssl_fromlabel_engine() to do provider lookup
only when engine is not set
(cherry picked from commit de486d0ec5d5642ddb1820a1269f5406a2bb1c64)
Use dst_key_t label to signal isprivate property as a downstream
alternative to upstream commit 74361b0b6e5a6b17ebeea6afe1ca990395d7a6dd.
That would require additional heavier changes.
Downstream change:
Move RSA bits check to legacy, let it use rsa_check for newer
rsabigexponent tests got broken by this change.
---
lib/dns/dst_openssl.h | 4 +
lib/dns/dst_parse.c | 21 ++---
lib/dns/openssl_link.c | 161 +++++++++++++++++++++++++++-----
lib/dns/openssldh_link.c | 5 +
lib/dns/opensslecdsa_link.c | 109 +++++++++++-----------
lib/dns/openssleddsa_link.c | 40 +++-----
lib/dns/opensslrsa_link.c | 181 ++++++++++++++----------------------
7 files changed, 296 insertions(+), 225 deletions(-)
diff --git a/lib/dns/dst_openssl.h b/lib/dns/dst_openssl.h
index 819af0f..cd386c0 100644
--- a/lib/dns/dst_openssl.h
+++ b/lib/dns/dst_openssl.h
@@ -64,4 +64,8 @@ ENGINE *
dst__openssl_getengine(const char *engine);
#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+isc_result_t
+dst__openssl_fromlabel(int key_base_id, const char *engine, const char *label,
+ const char *pin, EVP_PKEY **ppub, EVP_PKEY **ppriv);
+
ISC_LANG_ENDDECLS
diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c
index a353b86..7f3fe51 100644
--- a/lib/dns/dst_parse.c
+++ b/lib/dns/dst_parse.c
@@ -195,10 +195,9 @@ check_rsa(const dst_private_t *priv, bool external) {
mask = (1ULL << TAG_SHIFT) - 1;
- if (have[TAG_RSA_ENGINE & mask]) {
+ if (have[TAG_RSA_LABEL & mask]) {
ok = have[TAG_RSA_MODULUS & mask] &&
- have[TAG_RSA_PUBLICEXPONENT & mask] &&
- have[TAG_RSA_LABEL & mask];
+ have[TAG_RSA_PUBLICEXPONENT & mask];
} else {
ok = have[TAG_RSA_MODULUS & mask] &&
have[TAG_RSA_PUBLICEXPONENT & mask] &&
@@ -259,11 +258,9 @@ check_ecdsa(const dst_private_t *priv, bool external) {
mask = (1ULL << TAG_SHIFT) - 1;
- if (have[TAG_ECDSA_ENGINE & mask]) {
- ok = have[TAG_ECDSA_LABEL & mask];
- } else {
- ok = have[TAG_ECDSA_PRIVATEKEY & mask];
- }
+ ok = have[TAG_ECDSA_LABEL & mask] ||
+ have[TAG_ECDSA_PRIVATEKEY & mask];
+
return ok ? 0 : -1;
}
@@ -295,11 +292,9 @@ check_eddsa(const dst_private_t *priv, bool external) {
mask = (1ULL << TAG_SHIFT) - 1;
- if (have[TAG_EDDSA_ENGINE & mask]) {
- ok = have[TAG_EDDSA_LABEL & mask];
- } else {
- ok = have[TAG_EDDSA_PRIVATEKEY & mask];
- }
+ ok = have[TAG_EDDSA_LABEL & mask] ||
+ have[TAG_EDDSA_PRIVATEKEY & mask];
+
return ok ? 0 : -1;
}
diff --git a/lib/dns/openssl_link.c b/lib/dns/openssl_link.c
index e3a89f4..2495be4 100644
--- a/lib/dns/openssl_link.c
+++ b/lib/dns/openssl_link.c
@@ -44,6 +44,9 @@
#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
#include <openssl/engine.h>
#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#include <openssl/store.h>
+#endif
#include "openssl_shim.h"
@@ -51,6 +54,12 @@
static ENGINE *e = NULL;
#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+#define DST_RET(a) \
+ { \
+ result = a; \
+ goto cleanup; \
+ }
+
static void
enable_fips_mode(void) {
#ifdef HAVE_FIPS_MODE
@@ -70,32 +79,28 @@ enable_fips_mode(void) {
isc_result_t
dst__openssl_init(const char *engine) {
- isc_result_t result = ISC_R_SUCCESS;
-
enable_fips_mode();
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
if (engine != NULL && *engine == '\0') {
engine = NULL;
}
- if (engine != NULL) {
- e = ENGINE_by_id(engine);
- if (e == NULL) {
- result = DST_R_NOENGINE;
- goto cleanup_rm;
- }
- if (!ENGINE_init(e)) {
- result = DST_R_NOENGINE;
- goto cleanup_rm;
- }
- /* This will init the engine. */
- if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
- result = DST_R_NOENGINE;
- goto cleanup_init;
- }
+ if (engine == NULL) {
+ return (ISC_R_SUCCESS);
}
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ e = ENGINE_by_id(engine);
+ if (e == NULL) {
+ goto cleanup_rm;
+ }
+ if (!ENGINE_init(e)) {
+ goto cleanup_rm;
+ }
+ /* This will init the engine. */
+ if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
+ goto cleanup_init;
+ }
return ISC_R_SUCCESS;
cleanup_init:
ENGINE_finish(e);
@@ -105,10 +110,8 @@ cleanup_rm:
}
e = NULL;
ERR_clear_error();
-#else
- UNUSED(engine);
#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
- return result;
+ return (DST_R_NOENGINE);
}
void
@@ -220,4 +223,120 @@ dst__openssl_getengine(const char *engine) {
}
#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+static isc_result_t
+dst__openssl_fromlabel_engine(int key_base_id, const char *engine,
+ const char *label,
+ EVP_PKEY **ppub, EVP_PKEY **ppriv) {
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ isc_result_t result = ISC_R_SUCCESS;
+ ENGINE *e = NULL;
+ EVP_PKEY *pkey = NULL, *pubpkey = NULL;
+
+ UNUSED(key_base_id);
+
+ e = dst__openssl_getengine(engine);
+ if (e == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_NOENGINE));
+ }
+
+ pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
+ if (pubpkey == NULL) {
+ DST_RET(dst__openssl_toresult2("ENGINE_load_public_key",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_get_base_id(pubpkey) != key_base_id) {
+ DST_RET(DST_R_BADKEYTYPE);
+ }
+ pkey = ENGINE_load_private_key(e, label, NULL, NULL);
+ if (pkey == NULL) {
+ DST_RET(dst__openssl_toresult2("ENGINE_load_private_key",
+ DST_R_OPENSSLFAILURE));
+ }
+ if (EVP_PKEY_base_id(pkey) != key_base_id) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ *ppub = pubpkey;
+ *ppriv = pkey;
+cleanup:
+ return result;
+#else
+ UNUSED(key_base_id);
+ UNUSED(engine);
+ UNUSED(label);
+ UNUSED(ppub);
+ UNUSED(ppriv);
+ return DST_R_NOENGINE;
+#endif
+}
+
+
+static isc_result_t
+dst__openssl_fromlabel_provider(int key_base_id, const char *label, const char *pin,
+ EVP_PKEY **ppub, EVP_PKEY **ppriv) {
+ UNUSED(pin);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ isc_result_t result = DST_R_OPENSSLFAILURE;
+ OSSL_STORE_CTX *ctx = NULL;
+
+
+ ctx = OSSL_STORE_open(label, NULL, NULL, NULL, NULL);
+ if (!ctx) {
+ DST_RET(dst__openssl_toresult2("OSSL_STORE_open_ex",
+ DST_R_OPENSSLFAILURE));
+ }
+
+ while (!OSSL_STORE_eof(ctx)) {
+ OSSL_STORE_INFO *info = OSSL_STORE_load(ctx);
+ if (info == NULL) {
+ continue;
+ }
+ switch (OSSL_STORE_INFO_get_type(info)) {
+ case OSSL_STORE_INFO_PKEY:
+ if (*ppriv != NULL) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+ *ppriv = OSSL_STORE_INFO_get1_PKEY(info);
+ if (EVP_PKEY_get_base_id(*ppriv) != key_base_id) {
+ DST_RET(DST_R_BADKEYTYPE);
+ }
+ break;
+ case OSSL_STORE_INFO_PUBKEY:
+ if (*ppub != NULL) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+ *ppub = OSSL_STORE_INFO_get1_PUBKEY(info);
+ if (EVP_PKEY_get_base_id(*ppub) != key_base_id) {
+ DST_RET(DST_R_BADKEYTYPE);
+ }
+ break;
+ }
+ OSSL_STORE_INFO_free(info);
+ }
+ if (*ppriv != NULL && *ppub != NULL) {
+ result = ISC_R_SUCCESS;
+ }
+cleanup:
+ OSSL_STORE_close(ctx);
+ return result;
+#else
+ UNUSED(key_base_id);
+ UNUSED(label);
+ UNUSED(ppub);
+ UNUSED(ppriv);
+ return (DST_R_OPENSSLFAILURE);
+#endif
+}
+
+isc_result_t
+dst__openssl_fromlabel(int key_base_id, const char *engine, const char *label,
+ const char *pin, EVP_PKEY **ppub, EVP_PKEY **ppriv) {
+ if (engine == NULL) {
+ return (dst__openssl_fromlabel_provider(key_base_id, label,
+ pin, ppub, ppriv));
+ }
+
+ return (dst__openssl_fromlabel_engine(key_base_id, engine, label,
+ ppub, ppriv));
+}
+
/*! \file */
diff --git a/lib/dns/openssldh_link.c b/lib/dns/openssldh_link.c
index a4ba0f7..38345e6 100644
--- a/lib/dns/openssldh_link.c
+++ b/lib/dns/openssldh_link.c
@@ -610,6 +610,11 @@ err:
static bool
openssldh_isprivate(const dst_key_t *key) {
+ if (key->label != NULL) {
+ /* assume that _fromlabel will not pass without loading private key,
+ * but for non-exportable key cannot get d value on the object. */
+ return true;
+ }
#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
DH *dh = key->keydata.dh;
const BIGNUM *priv_key = NULL;
diff --git a/lib/dns/opensslecdsa_link.c b/lib/dns/opensslecdsa_link.c
index af45fdc..8b49b5d 100644
--- a/lib/dns/opensslecdsa_link.c
+++ b/lib/dns/opensslecdsa_link.c
@@ -617,6 +617,12 @@ opensslecdsa_isprivate(const dst_key_t *key) {
return false;
}
+ if (key->label != NULL) {
+ /* assume that _fromlabel will not pass without loading private key,
+ * but for non-exportable key cannot get d value on the object. */
+ return true;
+ }
+
#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
eckey = EVP_PKEY_get1_EC_KEY(pkey);
@@ -916,7 +922,7 @@ cleanup:
#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
static isc_result_t
-ecdsa_check(EC_KEY *eckey, EC_KEY *pubeckey) {
+ecdsa_check_legacy(EC_KEY *eckey, EC_KEY *pubeckey) {
const EC_POINT *pubkey;
pubkey = EC_KEY_get0_public_key(eckey);
@@ -937,9 +943,42 @@ ecdsa_check(EC_KEY *eckey, EC_KEY *pubeckey) {
return ISC_R_FAILURE;
}
+
+static isc_result_t
+ecdsa_check(EVP_PKEY **pkey, EVP_PKEY *pubpkey, int group_nid) {
+ isc_result_t result = ISC_R_FAILURE;
+ EC_KEY *eckey;
+ EC_KEY *pubeckey;
+
+ eckey = EVP_PKEY_get1_EC_KEY(*pkey);
+ if (eckey == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != group_nid) {
+ DST_RET(DST_R_INVALIDPRIVATEKEY);
+ }
+
+ pubeckey = EVP_PKEY_get1_EC_KEY(pubpkey);
+ if (pubeckey == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+ if (EC_GROUP_get_curve_name(EC_KEY_get0_group(pubeckey)) != group_nid) {
+ DST_RET(DST_R_INVALIDPUBLICKEY);
+ }
+
+ CHECK(ecdsa_check_legacy(eckey, pubeckey));
+cleanup:
+ if (pubeckey != NULL) {
+ EC_KEY_free(pubeckey);
+ }
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+ return result;
+}
#else
static isc_result_t
-ecdsa_check(EVP_PKEY **pkey, EVP_PKEY *pubpkey) {
+ecdsa_check(EVP_PKEY **pkey, EVP_PKEY *pubpkey, int group_nid) {
isc_result_t result = ISC_R_FAILURE;
int status;
size_t pkey_len = 0;
@@ -954,6 +993,8 @@ ecdsa_check(EVP_PKEY **pkey, EVP_PKEY *pubpkey) {
EVP_PKEY_CTX *ctx = NULL;
EVP_PKEY *pkey_new = NULL;
+ UNUSED(group_nid);
+
/* Check if `pkey` has a public key. */
status = EVP_PKEY_get_octet_string_param(*pkey, OSSL_PKEY_PARAM_PUB_KEY,
NULL, 0, &pkey_len);
@@ -1267,7 +1308,7 @@ opensslecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
pubeckey = EVP_PKEY_get1_EC_KEY(pub->keydata.pkey);
}
- if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) {
+ if (ecdsa_check_legacy(eckey, pubeckey) != ISC_R_SUCCESS) {
DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
}
@@ -1276,7 +1317,7 @@ opensslecdsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
}
#else
if (ecdsa_check(&key->keydata.pkey,
- pub == NULL ? NULL : pub->keydata.pkey) !=
+ pub == NULL ? NULL : pub->keydata.pkey, NID_undef) !=
ISC_R_SUCCESS)
{
DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
@@ -1309,11 +1350,7 @@ cleanup:
static isc_result_t
opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
const char *pin) {
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
isc_result_t result = ISC_R_SUCCESS;
- ENGINE *e;
- EC_KEY *eckey = NULL;
- EC_KEY *pubeckey = NULL;
int group_nid;
EVP_PKEY *pkey = NULL;
EVP_PKEY *pubpkey = NULL;
@@ -1323,13 +1360,9 @@ opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
UNUSED(pin);
- if (engine == NULL || label == NULL) {
+ if (label == NULL) {
return DST_R_NOENGINE;
}
- e = dst__openssl_getengine(engine);
- if (e == NULL) {
- DST_RET(DST_R_NOENGINE);
- }
if (key->key_alg == DST_ALG_ECDSA256) {
group_nid = NID_X9_62_prime256v1;
@@ -1337,48 +1370,27 @@ opensslecdsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
group_nid = NID_secp384r1;
}
- /* Load private key. */
- pkey = ENGINE_load_private_key(e, label, NULL, NULL);
- if (pkey == NULL) {
- DST_RET(dst__openssl_toresult2("ENGINE_load_private_key",
- DST_R_OPENSSLFAILURE));
- }
+ CHECK(dst__openssl_fromlabel(EVP_PKEY_EC, engine, label, pin,
+ &pubpkey, &pkey));
+
/* Check base id, group nid */
if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) {
DST_RET(DST_R_INVALIDPRIVATEKEY);
}
- eckey = EVP_PKEY_get1_EC_KEY(pkey);
- if (eckey == NULL) {
- DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
- }
- if (EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)) != group_nid) {
- DST_RET(DST_R_INVALIDPRIVATEKEY);
- }
-
- /* Load public key. */
- pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
- if (pubpkey == NULL) {
- DST_RET(dst__openssl_toresult2("ENGINE_load_public_key",
- DST_R_OPENSSLFAILURE));
- }
/* Check base id, group nid */
if (EVP_PKEY_base_id(pubpkey) != EVP_PKEY_EC) {
DST_RET(DST_R_INVALIDPUBLICKEY);
}
- pubeckey = EVP_PKEY_get1_EC_KEY(pubpkey);
- if (pubeckey == NULL) {
- DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
- }
- if (EC_GROUP_get_curve_name(EC_KEY_get0_group(pubeckey)) != group_nid) {
- DST_RET(DST_R_INVALIDPUBLICKEY);
- }
- if (ecdsa_check(eckey, pubeckey) != ISC_R_SUCCESS) {
+ if (ecdsa_check(&pkey, pubpkey, group_nid) != ISC_R_SUCCESS) {
DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
}
+ if (engine != NULL)
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ else
+ key->engine = NULL;
key->label = isc_mem_strdup(key->mctx, label);
- key->engine = isc_mem_strdup(key->mctx, engine);
key->key_size = EVP_PKEY_bits(pkey);
key->keydata.pkey = pkey;
pkey = NULL;
@@ -1390,21 +1402,8 @@ cleanup:
if (pkey != NULL) {
EVP_PKEY_free(pkey);
}
- if (pubeckey != NULL) {
- EC_KEY_free(pubeckey);
- }
- if (eckey != NULL) {
- EC_KEY_free(eckey);
- }
return result;
-#else
- UNUSED(key);
- UNUSED(engine);
- UNUSED(label);
- UNUSED(pin);
- return DST_R_NOENGINE;
-#endif /* !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
}
static dst_func_t opensslecdsa_functions = {
diff --git a/lib/dns/openssleddsa_link.c b/lib/dns/openssleddsa_link.c
index 6301db4..08d505b 100644
--- a/lib/dns/openssleddsa_link.c
+++ b/lib/dns/openssleddsa_link.c
@@ -362,6 +362,12 @@ openssleddsa_isprivate(const dst_key_t *key) {
return false;
}
+ if (key->label != NULL) {
+ /* assume that _fromlabel will not pass without loading private key,
+ * but for non-exportable key cannot get d value on the object. */
+ return true;
+ }
+
/* Must have a buffer to actually check if there is a private key. */
if (EVP_PKEY_get_raw_private_key(pkey, buf, &len) == 1) {
return true;
@@ -591,9 +597,7 @@ cleanup:
static isc_result_t
openssleddsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
const char *pin) {
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
isc_result_t result;
- ENGINE *e;
EVP_PKEY *pkey = NULL, *pubpkey = NULL;
int baseid = EVP_PKEY_NONE;
@@ -616,28 +620,17 @@ openssleddsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
return ISC_R_NOTIMPLEMENTED;
}
- if (engine == NULL) {
- return DST_R_NOENGINE;
- }
- e = dst__openssl_getengine(engine);
- if (e == NULL) {
- return DST_R_NOENGINE;
- }
- pkey = ENGINE_load_private_key(e, label, NULL, NULL);
- if (pkey == NULL) {
- return dst__openssl_toresult2("ENGINE_load_private_key",
- ISC_R_NOTFOUND);
- }
- if (EVP_PKEY_base_id(pkey) != baseid) {
- DST_RET(DST_R_INVALIDPRIVATEKEY);
- }
+ DST_RET(dst__openssl_fromlabel(baseid, engine, label, pin,
+ &pubpkey, &pkey));
- pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
- if (eddsa_check(pkey, pubpkey) != ISC_R_SUCCESS) {
+ if (EVP_PKEY_base_id(pkey) != baseid) {
DST_RET(DST_R_INVALIDPRIVATEKEY);
}
- key->engine = isc_mem_strdup(key->mctx, engine);
+ if (engine != NULL)
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ else
+ key->engine = NULL;
key->label = isc_mem_strdup(key->mctx, label);
key->key_size = EVP_PKEY_bits(pkey);
key->keydata.pkey = pkey;
@@ -652,13 +645,6 @@ cleanup:
EVP_PKEY_free(pkey);
}
return result;
-#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
- UNUSED(key);
- UNUSED(engine);
- UNUSED(label);
- UNUSED(pin);
- return DST_R_NOENGINE;
-#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
}
static dst_func_t openssleddsa_functions = {
diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c
index b92e1bf..12210e8 100644
--- a/lib/dns/opensslrsa_link.c
+++ b/lib/dns/opensslrsa_link.c
@@ -545,6 +545,12 @@ opensslrsa_isprivate(const dst_key_t *key) {
return false;
}
+ if (key->label != NULL) {
+ /* assume that _fromlabel will not pass without loading private key,
+ * but for non-exportable key cannot get d value on the object. */
+ return true;
+ }
+
#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
rsa = EVP_PKEY_get1_RSA(pkey);
INSIST(rsa != NULL);
@@ -995,7 +1001,7 @@ cleanup:
#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
static isc_result_t
-rsa_check(RSA *rsa, RSA *pub) {
+rsa_check_legacy(RSA *rsa, RSA *pub) {
const BIGNUM *n1 = NULL, *n2 = NULL;
const BIGNUM *e1 = NULL, *e2 = NULL;
BIGNUM *n = NULL, *e = NULL;
@@ -1050,6 +1056,46 @@ rsa_check(RSA *rsa, RSA *pub) {
return ISC_R_SUCCESS;
}
+
+static isc_result_t
+rsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) {
+ isc_result_t ret = ISC_R_FAILURE;
+ RSA *rsa = NULL, *pubrsa = NULL;
+ const BIGNUM *ex = NULL;
+
+ pubrsa = EVP_PKEY_get1_RSA(pubpkey);
+ if (pubrsa == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ rsa = EVP_PKEY_get1_RSA(pkey);
+ if (rsa == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
+ }
+
+ ret = rsa_check_legacy(rsa, pubrsa);
+ if (ret != ISC_R_SUCCESS) {
+ DST_RET(ret);
+ }
+
+ RSA_get0_key(rsa, NULL, &ex, NULL);
+
+ if (ex == NULL) {
+ DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
+ }
+ if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) {
+ DST_RET(ISC_R_RANGE);
+ }
+
+err:
+ if (rsa != NULL) {
+ RSA_free(rsa);
+ }
+ if (pubrsa != NULL) {
+ RSA_free(pubrsa);
+ }
+ return ret;
+}
#else
static isc_result_t
rsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) {
@@ -1097,6 +1143,10 @@ rsa_check(EVP_PKEY *pkey, EVP_PKEY *pubpkey) {
}
}
+ if (BN_num_bits(e1) > RSA_MAX_PUBEXP_BITS) {
+ DST_RET(ISC_R_RANGE);
+ }
+
if (EVP_PKEY_eq(pkey, pubpkey) == 1) {
DST_RET(ISC_R_SUCCESS);
}
@@ -1119,6 +1169,10 @@ cleanup:
}
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
+static isc_result_t
+opensslrsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
+ const char *pin);
+
static isc_result_t
opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
dst_private_t priv;
@@ -1131,12 +1185,8 @@ opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
OSSL_PARAM *params = NULL;
EVP_PKEY_CTX *ctx = NULL;
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
- const BIGNUM *ex = NULL;
- ENGINE *ep = NULL;
- const char *engine = NULL;
-#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
isc_mem_t *mctx = NULL;
+ const char *engine = NULL;
const char *label = NULL;
EVP_PKEY *pkey = NULL;
BIGNUM *n = NULL, *e = NULL, *d = NULL;
@@ -1190,46 +1240,7 @@ opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
* See if we can fetch it.
*/
if (label != NULL) {
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
- if (engine == NULL) {
- DST_RET(DST_R_NOENGINE);
- }
- ep = dst__openssl_getengine(engine);
- if (ep == NULL) {
- DST_RET(dst__openssl_toresult(DST_R_NOENGINE));
- }
- pkey = ENGINE_load_private_key(ep, label, NULL, NULL);
- if (pkey == NULL) {
- DST_RET(dst__openssl_toresult2("ENGINE_load_private_"
- "key",
- ISC_R_NOTFOUND));
- }
- key->engine = isc_mem_strdup(key->mctx, engine);
- key->label = isc_mem_strdup(key->mctx, label);
-
- rsa = EVP_PKEY_get1_RSA(pkey);
- if (rsa == NULL) {
- DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
- }
- if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
- DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
- }
- RSA_get0_key(rsa, NULL, &ex, NULL);
-
- if (ex == NULL) {
- DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
- }
- if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) {
- DST_RET(ISC_R_RANGE);
- }
-
- key->key_size = EVP_PKEY_bits(pkey);
- key->keydata.pkey = pkey;
- pkey = NULL;
- DST_RET(ISC_R_SUCCESS);
-#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
- DST_RET(DST_R_NOENGINE);
-#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
+ DST_RET(opensslrsa_fromlabel(key, engine, label, NULL));
}
for (i = 0; i < priv.nelements; i++) {
@@ -1318,9 +1329,14 @@ opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
BN_clear_free(iqmp);
}
}
- if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
+ if (rsa_check_legacy(rsa, pubrsa) != ISC_R_SUCCESS) {
DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
}
+
+ if (BN_num_bits(e) > RSA_MAX_PUBEXP_BITS) {
+ DST_RET(ISC_R_RANGE);
+ }
+
#else
bld = OSSL_PARAM_BLD_new();
if (bld == NULL) {
@@ -1387,17 +1403,9 @@ opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) {
DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
}
- if (rsa_check(pkey, pub != NULL ? pub->keydata.pkey : NULL) !=
- ISC_R_SUCCESS)
- {
- DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
- }
+ CHECK(rsa_check(pkey, pub != NULL ? pub->keydata.pkey : NULL));
#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 */
- if (BN_num_bits(e) > RSA_MAX_PUBEXP_BITS) {
- DST_RET(ISC_R_RANGE);
- }
-
key->key_size = BN_num_bits(n);
key->keydata.pkey = pkey;
pkey = NULL;
@@ -1461,69 +1469,31 @@ cleanup:
static isc_result_t
opensslrsa_fromlabel(dst_key_t *key, const char *engine, const char *label,
const char *pin) {
-#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
- ENGINE *e = NULL;
isc_result_t result = ISC_R_SUCCESS;
EVP_PKEY *pkey = NULL, *pubpkey = NULL;
- RSA *rsa = NULL, *pubrsa = NULL;
- const BIGNUM *ex = NULL;
UNUSED(pin);
- if (engine == NULL) {
- DST_RET(DST_R_NOENGINE);
- }
- e = dst__openssl_getengine(engine);
- if (e == NULL) {
- DST_RET(dst__openssl_toresult(DST_R_NOENGINE));
- }
+ CHECK(dst__openssl_fromlabel(EVP_PKEY_RSA, engine, label, pin,
+ &pubpkey, &pkey));
+ CHECK(rsa_check(pkey, pubpkey));
- pubpkey = ENGINE_load_public_key(e, label, NULL, NULL);
- if (pubpkey == NULL) {
- DST_RET(dst__openssl_toresult2("ENGINE_load_public_key",
- DST_R_OPENSSLFAILURE));
- }
- pubrsa = EVP_PKEY_get1_RSA(pubpkey);
- if (pubrsa == NULL) {
- DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
- }
-
- pkey = ENGINE_load_private_key(e, label, NULL, NULL);
if (pkey == NULL) {
- DST_RET(dst__openssl_toresult2("ENGINE_load_private_key",
+ DST_RET(dst__openssl_toresult2("dst__openssl_fromlabel",
DST_R_OPENSSLFAILURE));
}
- key->engine = isc_mem_strdup(key->mctx, engine);
+ if (engine != NULL)
+ key->engine = isc_mem_strdup(key->mctx, engine);
+ else
+ key->engine = NULL;
key->label = isc_mem_strdup(key->mctx, label);
- rsa = EVP_PKEY_get1_RSA(pkey);
- if (rsa == NULL) {
- DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE));
- }
- if (rsa_check(rsa, pubrsa) != ISC_R_SUCCESS) {
- DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
- }
- RSA_get0_key(rsa, NULL, &ex, NULL);
-
- if (ex == NULL) {
- DST_RET(dst__openssl_toresult(DST_R_INVALIDPRIVATEKEY));
- }
- if (BN_num_bits(ex) > RSA_MAX_PUBEXP_BITS) {
- DST_RET(ISC_R_RANGE);
- }
-
key->key_size = EVP_PKEY_bits(pkey);
key->keydata.pkey = pkey;
pkey = NULL;
cleanup:
- if (rsa != NULL) {
- RSA_free(rsa);
- }
- if (pubrsa != NULL) {
- RSA_free(pubrsa);
- }
if (pkey != NULL) {
EVP_PKEY_free(pkey);
}
@@ -1531,13 +1501,6 @@ cleanup:
EVP_PKEY_free(pubpkey);
}
return result;
-#else /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
- UNUSED(key);
- UNUSED(engine);
- UNUSED(label);
- UNUSED(pin);
- return DST_R_NOENGINE;
-#endif /* if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000 */
}
static dst_func_t opensslrsa_functions = {
--
2.52.0

View file

@ -0,0 +1,114 @@
From c5c756ce2ac4c1563d024428e148ca27c7721f71 Mon Sep 17 00:00:00 2001
From: Aram Sargsyan <aram@isc.org>
Date: Wed, 21 Sep 2022 15:05:11 +0000
Subject: [PATCH 2/3] Document nsupdate options related to DoT
Add documentation for the newly implemented DoT feature of the
nsupdate program.
(cherry picked from commit bd8299d7b501234263a6aee98049f879b1c700b7)
---
bin/nsupdate/nsupdate.rst | 48 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
diff --git a/bin/nsupdate/nsupdate.rst b/bin/nsupdate/nsupdate.rst
index 81bb4815cf4..f1ab5c76fa7 100644
--- a/bin/nsupdate/nsupdate.rst
+++ b/bin/nsupdate/nsupdate.rst
@@ -19,7 +19,7 @@ nsupdate - dynamic DNS update utility
Synopsis
~~~~~~~~
-:program:`nsupdate` [**-d**] [**-D**] [**-i**] [**-L** level] [ [**-g**] | [**-o**] | [**-l**] | [**-y** [hmac:]keyname:secret] | [**-k** keyfile] ] [**-t** timeout] [**-u** udptimeout] [**-r** udpretries] [**-v**] [**-T**] [**-P**] [**-V**] [ [**-4**] | [**-6**] ] [filename]
+:program:`nsupdate` [**-d**] [**-D**] [**-i**] [**-L** level] [ [**-g**] | [**-o**] | [**-l**] | [**-y** [hmac:]keyname:secret] | [**-k** keyfile] ] [ [**-S**] [**-K** tlskeyfile] [**-E** tlscertfile] [**-A** tlscafile] [**-H** tlshostname] [-O] ] [**-t** timeout] [**-u** udptimeout] [**-r** udpretries] [**-v**] [**-T**] [**-P**] [**-V**] [ [**-4**] | [**-6**] ] [filename]
Description
~~~~~~~~~~~
@@ -71,6 +71,15 @@ Options
This option sets use of IPv6 only.
+.. option:: -A tlscafile
+
+ This option specifies the file of the certificate authorities (CA) certificates
+ (in PEM format) in order to verify the remote server TLS certificate when
+ using DNS-over-TLS (DoT), to achieve Strict or Mutual TLS. When used, it will
+ override the certificates from the global certificates store, which are
+ otherwise used by default when :option:`-S` is enabled. This option can not
+ be used in conjuction with :option:`-O`, and it implies :option:`-S`.
+
.. option:: -C
Overrides the default `resolv.conf` file. This is only intended for testing.
@@ -84,10 +93,23 @@ Options
This option sets extra debug mode.
+.. option:: -E tlscertfile
+
+ This option sets the certificate(s) file for authentication for the
+ DNS-over-TLS (DoT) transport to the remote server. The certificate
+ chain file is expected to be in PEM format. This option implies :option:`-S`,
+ and can only be used with :option:`-K`.
+
.. option:: -g
This option enables standard GSS-TSIG mode.
+.. option:: -H tlshostname
+
+ This option makes :program:`nsupdate` use the provided hostname during remote
+ server TLS certificate verification. Otherwise, the DNS server name
+ is used. This option implies :option:`-S`.
+
.. option:: -i
This option forces interactive mode, even when standard input is not a terminal.
@@ -104,6 +126,13 @@ Options
key used to authenticate Dynamic DNS update requests. In this case,
the key specified is not an HMAC-MD5 key.
+.. option:: -K tlskeyfile
+
+ This option sets the key file for authenticated encryption for the
+ DNS-over-TLS (DoT) transport with the remote server. The private key file is
+ expected to be in PEM format. This option implies :option:`-S`, and can only
+ be used with :option:`-E`.
+
.. option:: -l
This option sets local-host only mode, which sets the server address to localhost
@@ -123,6 +152,14 @@ Options
This option enables a non-standards-compliant variant of GSS-TSIG
used by Windows 2000.
+.. option:: -O
+
+ This option enables Opportunistic TLS. When used, the remote peer's TLS
+ certificate will not be verified. This option should be used for debugging
+ purposes only, and it is not recommended to use it in production. This
+ option can not be used in conjuction with :option:`-A`, and it implies
+ :option:`-S`.
+
.. option:: -p port
This option sets the port to use for connections to a name server. The default is
@@ -138,6 +175,15 @@ Options
This option sets the number of UDP retries. The default is 3. If zero, only one update
request is made.
+.. option:: -S
+
+ This option indicates whether to use DNS-over-TLS (DoT) when querying
+ name servers specified by ``server servername port`` syntax in the input
+ file, and the primary server discovered through a SOA request. When the
+ :option:`-K` and :option:`-E` options are used, then the specified TLS
+ client certificate and private key pair are used for authentication
+ (Mutual TLS). This option implies :option:`-v`.
+
.. option:: -t timeout
This option sets the maximum time an update request can take before it is aborted. The
--
2.47.0

File diff suppressed because it is too large Load diff

1386
bind-9.20-nsupdate-tls.patch Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,45 @@
From 32f203d4e3c711cde5b1546a054be42b16436822 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
Date: Fri, 17 Jul 2026 19:42:39 +0200
Subject: [PATCH] Support program suffixes of tsig-confgen and ddns-confgen
Suffixes different than .exe are used on Fedora. But those commands
require exact names only. Allow switching between two variants only from
prefix. That should work on all platforms. It should support also names
like tsig-confgen-9.18 or tsig-confgen-9.20.
The same case applies to named-checkzone and named-compilezone.
---
bin/check/named-checkzone.c | 2 +-
bin/confgen/tsig-keygen.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/bin/check/named-checkzone.c b/bin/check/named-checkzone.c
index ae8feafc8c..5a1f712463 100644
--- a/bin/check/named-checkzone.c
+++ b/bin/check/named-checkzone.c
@@ -137,7 +137,7 @@ main(int argc, char **argv) {
}
#define PROGCMP(X) \
- (strcasecmp(prog_name, X) == 0 || strcasecmp(prog_name, X ".exe") == 0)
+ (strncasecmp(prog_name, X, strlen(X)) == 0)
if (PROGCMP("named-checkzone")) {
progmode = progmode_check;
diff --git a/bin/confgen/tsig-keygen.c b/bin/confgen/tsig-keygen.c
index f0d2f9a245..9b13312b5b 100644
--- a/bin/confgen/tsig-keygen.c
+++ b/bin/confgen/tsig-keygen.c
@@ -113,7 +113,7 @@ main(int argc, char **argv) {
}
#define PROGCMP(X) \
- (strcasecmp(progname, X) == 0 || strcasecmp(progname, X ".exe") == 0)
+ (strncasecmp(progname, X, strlen(X)) == 0)
if (PROGCMP("tsig-keygen")) {
progmode = progmode_keygen;
--
2.54.0

View file

@ -1,17 +1,28 @@
From 13348a5fc64387bf53ef450688e181100d0ceddb Mon Sep 17 00:00:00 2001
From: Petr Mensik <pemensik@redhat.com>
Date: Thu, 12 Dec 2024 15:56:13 +0100
Subject: [PATCH] Harden named service build flags
---
bin/named/Makefile.am | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/bin/named/Makefile.am b/bin/named/Makefile.am diff --git a/bin/named/Makefile.am b/bin/named/Makefile.am
index 57a023b..085f2f7 100644 index 57a023b..b832e9c 100644
--- a/bin/named/Makefile.am --- a/bin/named/Makefile.am
+++ b/bin/named/Makefile.am +++ b/bin/named/Makefile.am
@@ -32,9 +32,12 @@ AM_CPPFLAGS += \ @@ -33,7 +33,10 @@ endif HAVE_LIBXML2
endif HAVE_LIBXML2
AM_CPPFLAGS += \ AM_CPPFLAGS += \
+ -fpie \
-DNAMED_LOCALSTATEDIR=\"${localstatedir}\" \ -DNAMED_LOCALSTATEDIR=\"${localstatedir}\" \
-DNAMED_SYSCONFDIR=\"${sysconfdir}\" - -DNAMED_SYSCONFDIR=\"${sysconfdir}\"
+ -DNAMED_SYSCONFDIR=\"${sysconfdir}\" \
+AM_LDFLAGS += -pie -Wl,-z,relro,-z,now,-z,nodlopen,-z,noexecstack + -fpie
+ +
+AM_LDFLAGS += -pie -Wl,-z,relro,-z,now,-z,nodlopen,-z,noexecstack
sbin_PROGRAMS = named sbin_PROGRAMS = named
nodist_named_SOURCES = xsl.c --
2.47.1

38
bind-chroot.tmpfiles.d Normal file
View file

@ -0,0 +1,38 @@
# vim: ft=conf:
# TODO: these definitions are in different form in rpm spec %files chroot section
# find a way to have it defined only once
#defattr(0664,root,named,-)
c /var/named/chroot/dev/null 0664 root named - 1:3
c /var/named/chroot/dev/random 0664 root named - 1:8
c /var/named/chroot/dev/urandom 0664 root named - 1:9
c /var/named/chroot/dev/zero 0664 root named - 1:5
#defattr(0640,root,named,0750)
d /var/named/chroot 0750 root named -
d /var/named/chroot/dev 0750 root named -
d /var/named/chroot/etc 0750 root named -
d /var/named/chroot/etc/named 0750 root named -
d /var/named/chroot/etc/pki 0750 root named -
d /var/named/chroot/etc/pki/dnssec-keys 0750 root named -
d /var/named/chroot/etc/crypto-policies 0750 root named -
d /var/named/chroot/etc/crypto-policies/back-ends 0750 root named -
d /var/named/chroot/var 0750 root named -
d /var/named/chroot/run 0750 root named -
#defattr(-,root,root,-)
d /var/named/chroot/usr - root root -
d /var/named/chroot/usr/lib64 - root root -
d /var/named/chroot/usr/lib64/bind - root root -
d /var/named/chroot/usr/lib64/named - root root -
d /var/named/chroot/usr/share/GeoIP - root root -
d /var/named/chroot/usr/share/named - root root -
d /var/named/chroot/proc - root root -
d /var/named/chroot/proc/sys - root root -
d /var/named/chroot/proc/sys/net - root root -
d /var/named/chroot/proc/sys/net/ipv4 - root root -
#defattr(0660,root,named,01770)
d /var/named/chroot/var/named 01770 root named -
#defattr(0660,named,named,0770)
d /var/named/chroot/var/tmp 0770 named named -
d /var/named/chroot/var/log 0770 named named -
#defattr(-,named,named,-)
d /var/named/chroot/run/named - named named -
L /var/named/chroot/var/run - named named - ../run

3936
bind.spec

File diff suppressed because it is too large Load diff

View file

@ -1 +1,10 @@
# vim: ft=conf:
d /run/named 0755 named named - d /run/named 0755 named named -
d /var/named 01770 root named -
d /var/named/slaves 0770 named named -
d /var/named/data 0770 named named -
d /var/named/dynamic 0770 named named -
L /var/named/named.ca 0640 named named - ../../../etc/named.ca
L /var/named/named.localhost 0640 named named - ../../../usr/share/named/named.localhost
L /var/named/named.loopback 0640 named named - ../../../usr/share/named/named.loopback
L /var/named/named.empty 0640 named named - ../../../usr/share/named/named.empty

3190
changelog Normal file

File diff suppressed because it is too large Load diff

View file

@ -8,5 +8,5 @@ After=named-setup-rndc.service
[Service] [Service]
Type=oneshot Type=oneshot
RemainAfterExit=yes RemainAfterExit=yes
ExecStart=/usr/libexec/setup-named-chroot.sh /var/named/chroot on /etc/named-chroot.files ExecStart=/usr/libexec/%{name}/setup-named-chroot.sh /var/named/chroot on /etc/named-chroot.files
ExecStop=/usr/libexec/setup-named-chroot.sh /var/named/chroot off /etc/named-chroot.files ExecStop=/usr/libexec/%{name}/setup-named-chroot.sh /var/named/chroot off /etc/named-chroot.files

View file

@ -3,6 +3,7 @@
# if they are missing or empty in target directory. # if they are missing or empty in target directory.
/etc/localtime /etc/localtime
/etc/named.root.key /etc/named.root.key
/etc/named.ca
/etc/named.conf /etc/named.conf
/etc/named.rfc1912.zones /etc/named.rfc1912.zones
/etc/rndc.conf /etc/rndc.conf
@ -17,6 +18,7 @@
/usr/lib64/bind /usr/lib64/bind
/usr/lib/bind /usr/lib/bind
/usr/share/GeoIP /usr/share/GeoIP
/usr/share/named
/run/named /run/named
/proc/sys/net/ipv4/ip_local_port_range /proc/sys/net/ipv4/ip_local_port_range
# Warning: the order is important # Warning: the order is important

View file

@ -12,17 +12,18 @@ After=network.target
[Service] [Service]
Type=forking Type=forking
Environment=OPTIONS=
Environment=NAMEDCONF=/etc/named.conf Environment=NAMEDCONF=/etc/named.conf
EnvironmentFile=-/etc/sysconfig/named EnvironmentFile=-/etc/sysconfig/named
Environment=KRB5_KTNAME=/etc/named.keytab Environment=KRB5_KTNAME=/etc/named.keytab
PIDFile=/var/named/chroot/run/named/named.pid PIDFile=/var/named/chroot/run/named/named.pid
ExecStartPre=/bin/bash -c 'if [ ! "$DISABLE_ZONE_CHECKING" == "yes" ]; then /usr/bin/named-checkconf -t /var/named/chroot -z "$NAMEDCONF"; else echo "Checking of zone files is disabled"; fi' ExecStartPre=/bin/bash -c 'if [ ! "$DISABLE_ZONE_CHECKING" == "yes" ]; then %{_bindir}/named-checkconf%{program_suffix} -t /var/named/chroot -z "$NAMEDCONF"; else echo "Checking of zone files is disabled"; fi'
ExecStart=/usr/sbin/named -u named -c ${NAMEDCONF} -t /var/named/chroot $OPTIONS ExecStart=%{_sbindir}/named%{program_suffix} -u named -c ${NAMEDCONF} -t /var/named/chroot $OPTIONS
ExecReload=/bin/sh -c 'if /usr/sbin/rndc null > /dev/null 2>&1; then /usr/sbin/rndc reload; else /bin/kill -HUP $MAINPID; fi' ExecReload=/bin/sh -c 'if %{_sbindir}/rndc%{program_suffix} null > /dev/null 2>&1; then %{_sbindir}/rndc%{program_suffix} reload; else %{_bindir}/kill -HUP $MAINPID; fi'
ExecStop=/bin/sh -c '/usr/sbin/rndc stop > /dev/null 2>&1 || /bin/kill -TERM $MAINPID' ExecStop=/bin/sh -c '%{_sbindir}/rndc%{program_suffix} stop > /dev/null 2>&1 || %{_bindir}/kill -TERM $MAINPID'
PrivateTmp=false PrivateTmp=false
Restart=on-abnormal Restart=on-abnormal

View file

@ -4,4 +4,4 @@ Description=Generate rndc key for BIND (DNS)
[Service] [Service]
Type=oneshot Type=oneshot
ExecStart=/usr/libexec/generate-rndc-key.sh ExecStart=/usr/libexec/%{name}/generate-rndc-key.sh

View file

@ -51,7 +51,7 @@ logging {
zone "." IN { zone "." IN {
type hint; type hint;
file "named.ca"; file "/etc/named.ca";
}; };
include "/etc/named.rfc1912.zones"; include "/etc/named.rfc1912.zones";

View file

@ -2,11 +2,9 @@
missingok missingok
su named named su named named
create 0644 named named create 0644 named named
notifempty
postrotate postrotate
/usr/bin/systemctl reload named.service > /dev/null 2>&1 || true /usr/bin/systemctl reload named.service > /dev/null 2>&1 || true
/usr/bin/systemctl reload named-chroot.service > /dev/null 2>&1 || true /usr/bin/systemctl reload named-chroot.service > /dev/null 2>&1 || true
/usr/bin/systemctl reload named-sdb.service > /dev/null 2>&1 || true
/usr/bin/systemctl reload named-sdb-chroot.service > /dev/null 2>&1 || true
/usr/bin/systemctl reload named-pkcs11.service > /dev/null 2>&1 || true
endscript endscript
} }

View file

@ -1,4 +1,5 @@
// named.rfc1912.zones: // named.rfc1912.zones:
// vim: ft=named:
// //
// Provided by Red Hat caching-nameserver package // Provided by Red Hat caching-nameserver package
// //
@ -16,30 +17,30 @@
zone "localhost.localdomain" IN { zone "localhost.localdomain" IN {
type primary; type primary;
file "named.localhost"; file "/usr/share/named/named.localhost";
allow-update { none; }; allow-update { none; };
}; };
zone "localhost" IN { zone "localhost" IN {
type primary; type primary;
file "named.localhost"; file "/usr/share/named/named.localhost";
allow-update { none; }; allow-update { none; };
}; };
zone "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" IN { zone "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" IN {
type primary; type primary;
file "named.loopback"; file "/usr/share/named/named.loopback";
allow-update { none; }; allow-update { none; };
}; };
zone "1.0.0.127.in-addr.arpa" IN { zone "1.0.0.127.in-addr.arpa" IN {
type primary; type primary;
file "named.loopback"; file "/usr/share/named/named.loopback";
allow-update { none; }; allow-update { none; };
}; };
zone "0.in-addr.arpa" IN { zone "0.in-addr.arpa" IN {
type primary; type primary;
file "named.empty"; file "/usr/share/named/named.empty";
allow-update { none; }; allow-update { none; };
}; };

View file

@ -10,4 +10,9 @@ trust-anchors {
# managed key database will be trusted and maintained # managed key database will be trusted and maintained
# automatically. # automatically.
. initial-ds 20326 8 2 "E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D"; . initial-ds 20326 8 2 "E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D";
# This key (38696) will be pre-published in the root zone in 2025
# and is scheduled to begin signing in late 2026. At that time,
# servers which were already using the old key (20326) should roll
# seamlessly to this new one via RFC 5011 rollover.
. initial-ds 38696 8 2 "683D2D0ACB8C9B712A1948B27F741219298D0A450D612C483AF444A4C0FB2B16";
}; };

View file

@ -1,6 +0,0 @@
dirs /var/named
files /var/named/named.ca
files /var/named/named.empty
files /var/named/named.localhost
files /var/named/named.loopback

View file

@ -1,26 +0,0 @@
[Unit]
Description=Berkeley Internet Name Domain (DNS)
Wants=nss-lookup.target
Wants=named-setup-rndc.service
Before=nss-lookup.target
After=named-setup-rndc.service
After=network.target
[Service]
Type=forking
Environment=NAMEDCONF=/etc/named.conf
EnvironmentFile=-/etc/sysconfig/named
Environment=KRB5_KTNAME=/etc/named.keytab
PIDFile=/run/named/named.pid
ExecStartPre=/bin/bash -c 'if [ ! "$DISABLE_ZONE_CHECKING" == "yes" ]; then /usr/bin/named-checkconf -z "$NAMEDCONF"; else echo "Checking of zone files is disabled"; fi'
ExecStart=/usr/sbin/named -u named -c ${NAMEDCONF} $OPTIONS
ExecReload=/bin/sh -c 'if /usr/sbin/rndc null > /dev/null 2>&1; then /usr/sbin/rndc reload; else /bin/kill -HUP $MAINPID; fi'
ExecStop=/bin/sh -c '/usr/sbin/rndc stop > /dev/null 2>&1 || /bin/kill -TERM $MAINPID'
PrivateTmp=true
Restart=on-abnormal
[Install]
WantedBy=multi-user.target

28
named.service.in Normal file
View file

@ -0,0 +1,28 @@
[Unit]
Description=Berkeley Internet Name Domain (DNS)
Wants=nss-lookup.target
Wants=named-setup-rndc.service
Before=nss-lookup.target
After=named-setup-rndc.service
After=network.target
[Service]
Type=forking
Environment=OPTIONS=
Environment=NAMEDCONF=/etc/named.conf
EnvironmentFile=-/etc/sysconfig/named
Environment=KRB5_KTNAME=/etc/named.keytab
PIDFile=/run/named/named.pid
ExecStartPre=/bin/bash -c 'if [ ! "$DISABLE_ZONE_CHECKING" == "yes" ]; then %{_sbindir}/named-checkconf%{program_suffix} -z "$NAMEDCONF"; else echo "Checking of zone files is disabled"; fi'
ExecStart=%{_sbindir}/named%{program_suffix} -u named -c ${NAMEDCONF} $OPTIONS
ExecReload=/bin/sh -c 'if %{_sbindir}/rndc%{program_suffix} null > /dev/null 2>&1; then %{_sbindir}/rndc%{program_suffix} reload; else %{_bindir}/kill -HUP $MAINPID; fi'
ExecStop=/bin/sh -c '%{_sbindir}/rndc%{program_suffix} stop > /dev/null 2>&1 || %{_bindir}/kill -TERM $MAINPID'
PrivateTmp=true
Restart=on-abnormal
[Install]
WantedBy=multi-user.target

3
named.sysusers Normal file
View file

@ -0,0 +1,3 @@
#Type Name ID GECOS Home directory Shell
u named 25 "Named" /var/named /sbin/nologin
g named 25

View file

@ -3,7 +3,7 @@ environment+:
PACKAGE: bind PACKAGE: bind
discover: discover:
how: fmf how: fmf
url: https://src.fedoraproject.org/tests/bind.git url: https://gitlab.com/redhat/centos-stream/tests/bind.git
execute: execute:
how: tmt how: tmt
context: context:

View file

@ -3,7 +3,7 @@ environment+:
PACKAGE: bind PACKAGE: bind
discover: discover:
how: fmf how: fmf
url: https://src.fedoraproject.org/tests/bind.git url: https://gitlab.com/redhat/centos-stream/tests/bind.git
filter: 'tier: 1' filter: 'tier: 1'
execute: execute:
how: tmt how: tmt

View file

@ -1,2 +1,2 @@
SHA512 (bind-9.18.21.tar.xz) = 3cbc1775b6ca3d5eda0c277ab1246b5baa535dac53df3e60690aa7a2529bcb0fb644f7749b21b757870e5b3bc5f53ae9f0c0db182664de1b23f7e62c3fa8757d SHA512 (bind-9.18.50.tar.xz) = 5ae70c95f19868f41fd609bf34a908c38769401ee5b89e4782b94081032a579c00ba9010b2feb2789a95cfd12289cfe250e6488b3b47ff666bfa8cd7edf74d43
SHA512 (bind-9.18.21.tar.xz.asc) = 1afa22dddb5d6c42e2dea2369cc048d548ad86e3d215c692320716488419c012ac849f1699d61451353574ec4621fc93f7111658e9de7fa286336d97e33d5de6 SHA512 (bind-9.18.50.tar.xz.asc) = 41df6df786cd33055a3fd6632ffd2968d6600e314140c547bba3452ec9f7726f44df27c3bd53a5a937ad11407d7391641e0b7b1aff7ebf87f25c65e2a76707b6

View file

@ -1 +1,2 @@
. 3600 IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU= . 3600 IN DNSKEY 257 3 8 AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU=
. 3600 IN DNSKEY 257 3 8 AwEAAa96jeuknZlaeSrvyAJj6ZHv28hhOKkx3rLGXVaC6rXTsDc449/cidltpkyGwCJNnOAlFNKF2jBosZBU5eeHspaQWOmOElZsjICMQMC3aeHbGiShvZsx4wMYSjH8e7Vrhbu6irwCzVBApESjbUdpWWmEnhathWu1jo+siFUiRAAxm9qyJNg/wOZqqzL/dL/q8PkcRU5oUKEpUge71M3ej2/7CPqpdVwuMoTvoB+ZOT4YeGyxMvHmbrxlFzGOHOijtzN+u1TQNatX2XBuzZNQ1K+s2CXkPIZo7s6JgZyvaBevYtxPvYLw4z9mR7K2vaF18UYH9Z9GNUUeayffKC73PYc=