NSEC3 of removed empty-non-terminal remains in chain, breaking validation tools
Summary
A zone is being signed by an authoritative Bind9, including NSEC3 chain management. The zone contains a non-authoritative (opt-outed) NS record below an empty-non-terminal (e.g. nonauth.ent.example.com. NS
when ent.example.com.
has no records). I noticed that Bind9 created a NSEC3 for such empty-non-terminal (ent.example.com.
) although it's not necessary (but not entirely wrong as well, opt-out is always optional). The problem is, that if a dynamic DDNS update to the zone removes the underlying record, implicitly removing the empty-non-terminal as well, Bind9 does not remove this NSEC3 record, which is already clearly wrong.
After such operation, indeed, both dnssec-verify
and ldns-verify-zone
complain about this superfluous NSEC3 record. KnotDNS's zone-checking utilities don't, but we are going to fix them to check this as well (existing NSEC3 not belonging to any node in the zone). If the user uses such verifications tool to guard his signing processes, his zone may get blocked due to this bug, until he somehow forces Bind9 to reconstruct the NSEC3-chain completely.
BIND version used
BIND 9.18.12-0ubuntu0.22.04.1-Ubuntu (Extended Support Version) <id:>
running on Linux x86_64 5.15.0-70-generic #77-Ubuntu SMP Tue Mar 21 14:02:37 UTC 2023
built by make with '--build=x86_64-linux-gnu' '--prefix=/usr' '--includedir=${prefix}/include' '--mandir=${prefix}/share/man' '--infodir=${prefix}/share/info' '--sysconfdir=/etc' '--localstatedir=/var' '--disable-option-checking' '--disable-silent-rules' '--libdir=${prefix}/lib/x86_64-linux-gnu' '--runstatedir=/run' '--disable-maintainer-mode' '--disable-dependency-tracking' '--libdir=/usr/lib/x86_64-linux-gnu' '--sysconfdir=/etc/bind' '--with-python=python3' '--localstatedir=/' '--enable-threads' '--enable-largefile' '--with-libtool' '--enable-shared' '--disable-static' '--with-gost=no' '--with-openssl=/usr' '--with-gssapi=yes' '--with-libidn2' '--with-json-c' '--with-lmdb=/usr' '--with-gnu-ld' '--with-maxminddb' '--with-atf=no' '--enable-ipv6' '--enable-rrl' '--enable-filter-aaaa' '--disable-native-pkcs11' 'build_alias=x86_64-linux-gnu' 'CFLAGS=-g -O2 -ffile-prefix-map=/build/bind9-RW6AWX/bind9-9.18.12=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -fno-strict-aliasing -fno-delete-null-pointer-checks -DNO_VERSION_DATE -DDIG_SIGCHASE' 'LDFLAGS=-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro -Wl,-z,now' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2'
compiled by GCC 11.3.0
compiled with OpenSSL version: OpenSSL 3.0.2 15 Mar 2022
linked to OpenSSL version: OpenSSL 3.0.2 15 Mar 2022
compiled with libuv version: 1.43.0
linked to libuv version: 1.43.0
compiled with libnghttp2 version: 1.43.0
linked to libnghttp2 version: 1.43.0
compiled with libxml2 version: 2.9.13
linked to libxml2 version: 20913
compiled with json-c version: 0.15
linked to json-c version: 0.15
compiled with zlib version: 1.2.11
linked to zlib version: 1.2.11
linked to maxminddb version: 1.5.2
threads support is enabled
DNSSEC algorithms: RSASHA1 NSEC3RSASHA1 RSASHA256 RSASHA512 ECDSAP256SHA256 ECDSAP384SHA384 ED25519 ED448
DS algorithms: SHA-1 SHA-256 SHA-384
HMAC algorithms: HMAC-MD5 HMAC-SHA1 HMAC-SHA224 HMAC-SHA256 HMAC-SHA384 HMAC-SHA512
TKEY mode 2 support (Diffie-Hellman): yes
TKEY mode 3 support (GSS-API): yes
default paths:
named configuration: /etc/bind/named.conf
rndc configuration: /etc/bind/rndc.conf
DNSSEC root key: /etc/bind/bind.keys
nsupdate session key: //run/named/session.key
named PID file: //run/named/named.pid
named lock file: //run/named/named.lock
geoip-directory: /usr/share/GeoIP
Steps to reproduce
- prepare a zone with a NS record below an empty-non-terminal
- configure Bind9 for automatic signing with NSEC3 opt-out
- remove this record with DDNS
- observe that the NSEC3 record belonging to the empty-non-terminal remained in the signed zone
What is the current bug behavior?
Redundant NSEC3 present (orphaned) in the zone.
What is the expected correct behavior?
All zone verifications tools (incl dnssec-verify
) report no errors.
Relevant configuration files
If you really like me to attach machine-generated configuratoin files and zones from KnotDNS testing environment, please let me know. I didn't bother to prepare a minimized reproducer, sorry.
Relevant logs and/or screenshots
See above.
Possible fixes
I'm not really sure if the issue happens as well if the record removed is authoritative and not opt-outed. If not, one possible fix may be based on always opt-outing the empty-non-terminals, that only contain opt-outed delegations beneath them. But a straightforward fix may be also good. I really do understand that this will be difficult :)