inline-signing breaks nsdiff
Summary
When both inline-signing and update-policy are in use, I can't detect race conditions with the method described in RFC 2136 section 5.7, which nsdiff uses.
In a zone that uses dnssec-policy and relies on the default value of inline-signing, the method in RFC 2136 section 5.7 will stop working on upgrade to BIND 9.20, as inline-signing will then be switched on by default, if I understand correctly.
BIND version used
$ named -V
BIND 9.18.19-1~deb12u1-Debian (Extended Support Version) <id:>
running on Linux x86_64 5.10.0-25-amd64 #1 SMP Debian 5.10.191-1 (2023-08-16)
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' '--enable-dnstap' 'build_alias=x86_64-linux-gnu' 'CFLAGS=-g -O2 -ffile-prefix-map=/build/reproducible-path/bind9-9.18.19=. -fstack-protector-strong -Wformat -Werror=format-security -fno-strict-aliasing -fno-delete-null-pointer-checks -DNO_VERSION_DATE -DDIG_SIGCHASE' 'LDFLAGS=-Wl,-z,relro -Wl,-z,now' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2'
compiled by GCC 12.2.0
compiled with OpenSSL version: OpenSSL 3.0.10 1 Aug 2023
linked to OpenSSL version: OpenSSL 3.0.9 30 May 2023
compiled with libuv version: 1.44.2
linked to libuv version: 1.44.2
compiled with libnghttp2 version: 1.52.0
linked to libnghttp2 version: 1.52.0
compiled with libxml2 version: 2.9.14
linked to libxml2 version: 20914
compiled with json-c version: 0.16
linked to json-c version: 0.16
compiled with zlib version: 1.2.13
linked to zlib version: 1.2.13
linked to maxminddb version: 1.7.1
compiled with protobuf-c version: 1.4.1
linked to protobuf-c version: 1.4.1
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
Here I start from a working state with serial number 2023091800. The prerequisite matches the reply to the SOA query, and the update is answered with NOERROR. This is correct as far as I understand:
$ (echo 'prereq yxrrset xn--rombobjrn-67a.se. IN SOA ns1.xn--rombobjrn-67a.se. hostmaster.xn--rombobjrn-67a.se. 2023091800 14400 3600 3024000 86400' ; echo send) | nsupdate -k internal -d
Creating key...
Creating key...
namefromtext
keycreate
Reply from SOA query:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23595
;; flags: qr aa rd ra; QUESTION: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;xn--rombobjrn-67a.se. IN SOA
;; ANSWER SECTION:
xn--rombobjrn-67a.se. 86400 IN SOA ns1.xn--rombobjrn-67a.se. hostmaster.xn--rombobjrn-67a.se. 2023091800 14400 3600 3024000 86400
Found zone name: xn--rombobjrn-67a.se
The primary is: ns1.xn--rombobjrn-67a.se
Sending update to 192.168.72.1#53
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 44980
;; flags:; ZONE: 1, PREREQ: 1, UPDATE: 0, ADDITIONAL: 1
;; PREREQUISITE SECTION:
xn--rombobjrn-67a.se. 0 IN SOA ns1.xn--rombobjrn-67a.se. hostmaster.xn--rombobjrn-67a.se. 2023091800 14400 3600 3024000 86400
;; TSIG PSEUDOSECTION:
internal.beorn.tag.xn--rombobjrn-67a.se. 0 ANY TSIG hmac-sha512. 1695171088 300 64 [...] 44980 NOERROR 0
Reply from update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 44980
;; flags: qr; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 1
;; ZONE SECTION:
;xn--rombobjrn-67a.se. IN SOA
;; TSIG PSEUDOSECTION:
internal.beorn.tag.xn--rombobjrn-67a.se. 0 ANY TSIG hmac-sha512. 1695171088 300 64 [...] 44980 NOERROR 0
Later, the server has automatically increased the serial number to 2023091802. I use the new serial number in the prerequisite, so it looks identical to the new SOA value, but this time the update is rejected with NXRRSET:
$ (echo 'prereq yxrrset xn--rombobjrn-67a.se. IN SOA ns1.xn--rombobjrn-67a.se. hostmaster.xn--rombobjrn-67a.se. 2023091802 14400 3600 3024000 86400' ; echo send) | nsupdate -k internal -d
Creating key...
Creating key...
namefromtext
keycreate
Reply from SOA query:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 65052
;; flags: qr aa rd ra; QUESTION: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;xn--rombobjrn-67a.se. IN SOA
;; ANSWER SECTION:
xn--rombobjrn-67a.se. 86400 IN SOA ns1.xn--rombobjrn-67a.se. hostmaster.xn--rombobjrn-67a.se. 2023091802 14400 3600 3024000 86400
Found zone name: xn--rombobjrn-67a.se
The primary is: ns1.xn--rombobjrn-67a.se
Sending update to 192.168.72.1#53
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 52912
;; flags:; ZONE: 1, PREREQ: 1, UPDATE: 0, ADDITIONAL: 1
;; PREREQUISITE SECTION:
xn--rombobjrn-67a.se. 0 IN SOA ns1.xn--rombobjrn-67a.se. hostmaster.xn--rombobjrn-67a.se. 2023091802 14400 3600 3024000 86400
;; TSIG PSEUDOSECTION:
internal.beorn.tag.xn--rombobjrn-67a.se. 0 ANY TSIG hmac-sha512. 1695259647 300 64 [...] 52912 NOERROR 0
Reply from update query:
;; ->>HEADER<<- opcode: UPDATE, status: NXRRSET, id: 52912
;; flags: qr; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 1
;; ZONE SECTION:
;xn--rombobjrn-67a.se. IN SOA
;; TSIG PSEUDOSECTION:
internal.beorn.tag.xn--rombobjrn-67a.se. 0 ANY TSIG hmac-sha512. 1695259647 300 64 [...] 52912 NOERROR 0
Now I change the prerequisite back to 2023091800. The SOA hasn't changed again. The serial number is still 2023091802. This update should be rejected as the prerequisite contains an outdated serial number, but is in fact answered with NOERROR:
$ (echo 'prereq yxrrset xn--rombobjrn-67a.se. IN SOA ns1.xn--rombobjrn-67a.se. hostmaster.xn--rombobjrn-67a.se. 2023091800 14400 3600 3024000 86400' ; echo send) | nsupdate -k internal -d
Creating key...
Creating key...
namefromtext
keycreate
Reply from SOA query:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6226
;; flags: qr aa rd ra; QUESTION: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;xn--rombobjrn-67a.se. IN SOA
;; ANSWER SECTION:
xn--rombobjrn-67a.se. 86400 IN SOA ns1.xn--rombobjrn-67a.se. hostmaster.xn--rombobjrn-67a.se. 2023091802 14400 3600 3024000 86400
Found zone name: xn--rombobjrn-67a.se
The primary is: ns1.xn--rombobjrn-67a.se
Sending update to 192.168.72.1#53
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 49961
;; flags:; ZONE: 1, PREREQ: 1, UPDATE: 0, ADDITIONAL: 1
;; PREREQUISITE SECTION:
xn--rombobjrn-67a.se. 0 IN SOA ns1.xn--rombobjrn-67a.se. hostmaster.xn--rombobjrn-67a.se. 2023091800 14400 3600 3024000 86400
;; TSIG PSEUDOSECTION:
internal.beorn.tag.xn--rombobjrn-67a.se. 0 ANY TSIG hmac-sha512. 1695263626 300 64 [...] 49961 NOERROR 0
Reply from update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 49961
;; flags: qr; ZONE: 1, PREREQ: 0, UPDATE: 0, ADDITIONAL: 1
;; ZONE SECTION:
;xn--rombobjrn-67a.se. IN SOA
;; TSIG PSEUDOSECTION:
internal.beorn.tag.xn--rombobjrn-67a.se. 0 ANY TSIG hmac-sha512. 1695263626 300 64 [...] 49961 NOERROR 0
What is the current bug behavior?
It seems that a serial number specified in a prerequisite of an update is compared to the unsigned version of the zone, but the serial number retrieved with a SOA or AXFR query is from the signed version. As far as I know a client can't look up the unsigned serial number, and thus can't specify it in the prerequisite. Thus the update fails when the two serial numbers differ.
What is the expected correct behavior?
It seems to me that a prerequisite that specifies a SOA record should be checked against the same record that a client gets in response to a SOA or AXFR query. I don't know what other usecases that might break though.
Relevant excerpts from the configuration file
dnssec-policy "some_name" {
keys {
ksk lifetime unlimited algorithm rsasha256 2048;
zsk lifetime unlimited algorithm rsasha256 2048;
};
dnskey-ttl P1D;
purge-keys 0;
};
view "internal" {
allow-transfer { key internal.beorn.tag.xn--rombobjrn-67a.se.; };
zone "xn--rombobjrn-67a.se" {
type master;
file "/var/lib/bind/db.xn--rombobjrn-67a.se.internal";
dnssec-policy some_name;
parental-agents { ::1; };
inline-signing yes;
update-policy {
grant internal.beorn.tag.xn--rombobjrn-67a.se. zonesub ANY;
};
};
};