From dbd86cb6d2478aad99f8127009944e9c47ef8463 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Mon, 23 Mar 2026 15:15:18 +0000 Subject: [PATCH 1/5] Allow empty APL records Allow empty APL records because RFC 3123 (Section 4) says "zero or more items". This fixes processing of a catalog zone ACL (which is based on APL records) when the zone contains an empty APL record or when a zone update arrives which creates an empty APL record. (cherry picked from commit 35b8af229e82291fd635225ac45f853e7190f8b7) --- lib/dns/rdata/in_1/apl_42.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dns/rdata/in_1/apl_42.c b/lib/dns/rdata/in_1/apl_42.c index 67c021062b..ee33fa5c57 100644 --- a/lib/dns/rdata/in_1/apl_42.c +++ b/lib/dns/rdata/in_1/apl_42.c @@ -327,7 +327,7 @@ dns_rdata_apl_first(dns_rdata_in_apl_t *apl) { /* * If no APL return ISC_R_NOMORE. */ - if (apl->apl == NULL) { + if (apl->apl == NULL || apl->apl_len == 0) { return ISC_R_NOMORE; } From e3aa88080e9eff21209e862fac178d344c802184 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Mon, 23 Mar 2026 15:17:47 +0000 Subject: [PATCH 2/5] Test an empty APL record as catalog zone ACL Test that named can process an empty APL record as a valid catalog zone ACL. (cherry picked from commit b82966000eb4da912aacd390bf8040438e6071fe) --- bin/tests/system/catz/tests.sh | 96 ++++++++++++++++++++++++++ bin/tests/system/catz/tests_sh_catz.py | 1 + 2 files changed, 97 insertions(+) diff --git a/bin/tests/system/catz/tests.sh b/bin/tests/system/catz/tests.sh index b81b9582e8..44821209ed 100644 --- a/bin/tests/system/catz/tests.sh +++ b/bin/tests/system/catz/tests.sh @@ -2679,6 +2679,102 @@ wait_for_soa @10.53.0.4 tls1.example. dig.out.test$n || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) +########################################################################## +# GL #5801 + +nextpart ns4/named.run >/dev/null + +n=$((n + 1)) +echo_i "Add empty APL allow-query to catalog-misc zone using nsupdate ($n)" +ret=0 +# Using "\# 0" form as a workaround for nsupdate not parsing zero length rdata +$NSUPDATE -d <>nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 ${PORT} + update add allow-query.ext.catalog-misc.example. 3600 IN APL \# 0 + send +END +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "waiting for secondary to sync up ($n)" +ret=0 +wait_for_message ns4/named.run "catz: catalog-misc.example: reload done: success" || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "Adding a domain check-allow-query.example. to primary via RNDC ($n)" +ret=0 +echo "@ 3600 IN SOA . . 1 3600 3600 3600 3600" >ns1/check-allow-query.example.db +echo "@ 3600 IN NS invalid." >>ns1/check-allow-query.example.db +rndccmd 10.53.0.1 addzone check-allow-query.example. in default '{ type primary; file "check-allow-query.example.db"; allow-transfer { any; }; allow-update { any; }; notify explicit; also-notify { 10.53.0.4; }; };' || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking that check-allow-query.example. is now served by primary ($n)" +ret=0 +wait_for_soa @10.53.0.1 check-allow-query.example. dig.out.test$n || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +nextpart ns4/named.run >/dev/null + +n=$((n + 1)) +echo_i "Adding domain check-allow-query.example. to catalog-misc zone ($n)" +ret=0 +$NSUPDATE -d <>nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 ${PORT} + update add check-allow-query.zones.catalog-misc.example. 3600 IN PTR check-allow-query.example. + update add primaries.ext.check-allow-query.zones.catalog-misc.example. 3600 IN A 10.53.0.1 + send +END +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "waiting for secondary to sync up ($n)" +ret=0 +wait_for_message ns4/named.run "catz: adding zone 'check-allow-query.example' from catalog 'catalog-misc.example'" \ + && wait_for_message ns4/named.run "transfer of 'check-allow-query.example/IN' from 10.53.0.1#${PORT}: Transfer status: success" || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking that check-allow-query.example. is not served by secondary ($n)" +ret=0 +wait_for_soa @10.53.0.4 check-allow-query.example. dig.out.test$n && ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +nextpart ns4/named.run >/dev/null + +n=$((n + 1)) +echo_i "Deleting empty allow-query property from catalog-misc zone ($n)" +ret=0 +$NSUPDATE -d <>nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 ${PORT} + update delete allow-query.ext.catalog-misc.example. 3600 IN APL + send +END +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "waiting for secondary to sync up ($n)" +ret=0 +wait_for_message ns4/named.run "catz: catalog-misc.example: reload done: success" || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking that check-allow-query.example. is now served by secondary ($n)" +ret=0 +wait_for_soa @10.53.0.4 check-allow-query.example. dig.out.test$n || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + ########################################################################## # GL #5658 diff --git a/bin/tests/system/catz/tests_sh_catz.py b/bin/tests/system/catz/tests_sh_catz.py index 060e45fc0f..4b68e2f65e 100644 --- a/bin/tests/system/catz/tests_sh_catz.py +++ b/bin/tests/system/catz/tests_sh_catz.py @@ -21,6 +21,7 @@ pytestmark = pytest.mark.extra_artifacts( "ns*/*.nzd*", "ns*/catalog*.example.db", "ns*/*dom*.example.db", + "ns1/check-allow-query.example.db", "ns1/longlong.longlong.long.long.name.example.db", "ns1/tls1.example.db", "ns2/__catz__*.db", From 6159980235038137c33f19aea5c141047bee6e4d Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 25 Mar 2026 15:58:53 +1100 Subject: [PATCH 3/5] Test walking apl list entries (cherry picked from commit e435b0b7fb683ecf42ec53a4036bb8c6b9a168dc) --- tests/dns/rdata_test.c | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/tests/dns/rdata_test.c b/tests/dns/rdata_test.c index ab6b2a974c..4f724774a2 100644 --- a/tests/dns/rdata_test.c +++ b/tests/dns/rdata_test.c @@ -289,6 +289,20 @@ check_struct_conversions(dns_rdata_t *rdata, size_t structsize, * https/svcb parameters. */ switch (type) { + case dns_rdatatype_apl: { + dns_rdata_in_apl_t *apl = rdata_struct; + + for (result = dns_rdata_apl_first(apl); result == ISC_R_SUCCESS; + result = dns_rdata_apl_next(apl)) + { + dns_rdata_apl_ent_t apl_ent; + dns_rdata_apl_current(apl, &apl_ent); + count++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(count, loop); + break; + } case dns_rdatatype_hip: { dns_rdata_hip_t *hip = rdata_struct; @@ -874,23 +888,26 @@ key_required(void **state, dns_rdatatype_t type, size_t size) { ISC_RUN_TEST_IMPL(apl) { text_ok_t text_ok[] = { /* empty list */ - TEXT_VALID(""), + TEXT_VALID_LOOP(0, ""), /* min,max prefix IPv4 */ - TEXT_VALID("1:0.0.0.0/0"), TEXT_VALID("1:127.0.0.1/32"), + TEXT_VALID_LOOP(1, "1:0.0.0.0/0"), + TEXT_VALID_LOOP(1, "1:127.0.0.1/32"), /* min,max prefix IPv6 */ - TEXT_VALID("2:::/0"), TEXT_VALID("2:::1/128"), + TEXT_VALID_LOOP(1, "2:::/0"), TEXT_VALID_LOOP(1, "2:::1/128"), /* negated */ - TEXT_VALID("!1:0.0.0.0/0"), TEXT_VALID("!1:127.0.0.1/32"), - TEXT_VALID("!2:::/0"), TEXT_VALID("!2:::1/128"), + TEXT_VALID_LOOP(1, "!1:0.0.0.0/0"), + TEXT_VALID_LOOP(1, "!1:127.0.0.1/32"), + TEXT_VALID_LOOP(1, "!2:::/0"), TEXT_VALID_LOOP(1, "!2:::1/128"), /* bits set after prefix length - not disallowed */ - TEXT_VALID("1:127.0.0.0/0"), TEXT_VALID("2:8000::/0"), + TEXT_VALID_LOOP(1, "1:127.0.0.0/0"), + TEXT_VALID_LOOP(1, "2:8000::/0"), /* multiple */ - TEXT_VALID("1:0.0.0.0/0 1:127.0.0.1/32"), - TEXT_VALID("1:0.0.0.0/0 !1:127.0.0.1/32"), + TEXT_VALID_LOOP(2, "1:0.0.0.0/0 1:127.0.0.1/32"), + TEXT_VALID_LOOP(2, "1:0.0.0.0/0 !1:127.0.0.1/32"), /* family 0, prefix 0, positive */ - TEXT_VALID("\\# 4 00000000"), + TEXT_VALID_LOOP(1, "\\# 4 00000000"), /* family 0, prefix 0, negative */ - TEXT_VALID("\\# 4 00000080"), + TEXT_VALID_LOOP(1, "\\# 4 00000080"), /* prefix too long */ TEXT_INVALID("1:0.0.0.0/33"), TEXT_INVALID("2:::/129"), /* From 8a3408c3b1632e19e96e258cfe3b5c1a8feaa3a1 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 25 Mar 2026 16:13:16 +1100 Subject: [PATCH 4/5] Allow the dns_rdata_in_apl structure to be walked twice The offset value should be set prior to calculating the length. (cherry picked from commit f2fd54f4b2a14ab0a9c9eb18b1ddf913e3b29d12) --- lib/dns/rdata/in_1/apl_42.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/dns/rdata/in_1/apl_42.c b/lib/dns/rdata/in_1/apl_42.c index ee33fa5c57..ba0d3d041b 100644 --- a/lib/dns/rdata/in_1/apl_42.c +++ b/lib/dns/rdata/in_1/apl_42.c @@ -331,6 +331,8 @@ dns_rdata_apl_first(dns_rdata_in_apl_t *apl) { return ISC_R_NOMORE; } + apl->offset = 0; + /* * Sanity check data. */ @@ -338,7 +340,6 @@ dns_rdata_apl_first(dns_rdata_in_apl_t *apl) { length = apl->apl[apl->offset + 3] & 0x7f; INSIST(4 + length <= apl->apl_len); - apl->offset = 0; return ISC_R_SUCCESS; } From 9be3bccf6d487f3850970e0c13dbad00dec1d87a Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 25 Mar 2026 16:15:42 +1100 Subject: [PATCH 5/5] Test the ability to walk the iterators multiple times It should be possible to walk APL, HIP, HTTPS and SVBC record elements multiple times. We now test this. (cherry picked from commit aa2a41b2d1dd37092e06203b776f33a7aeb160a1) --- tests/dns/rdata_test.c | 94 ++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 40 deletions(-) diff --git a/tests/dns/rdata_test.c b/tests/dns/rdata_test.c index 4f724774a2..6dd6e0c0da 100644 --- a/tests/dns/rdata_test.c +++ b/tests/dns/rdata_test.c @@ -257,7 +257,6 @@ check_struct_conversions(dns_rdata_t *rdata, size_t structsize, isc_buffer_t target; void *rdata_struct; char buf[1024]; - unsigned int count = 0; rdata_struct = isc_mem_allocate(mctx, structsize); assert_non_null(rdata_struct); @@ -292,64 +291,79 @@ check_struct_conversions(dns_rdata_t *rdata, size_t structsize, case dns_rdatatype_apl: { dns_rdata_in_apl_t *apl = rdata_struct; - for (result = dns_rdata_apl_first(apl); result == ISC_R_SUCCESS; - result = dns_rdata_apl_next(apl)) - { - dns_rdata_apl_ent_t apl_ent; - dns_rdata_apl_current(apl, &apl_ent); - count++; + for (size_t pass = 1; pass < 3; pass++) { + unsigned int count = 0; + for (result = dns_rdata_apl_first(apl); + result == ISC_R_SUCCESS; + result = dns_rdata_apl_next(apl)) + { + dns_rdata_apl_ent_t apl_ent; + dns_rdata_apl_current(apl, &apl_ent); + count++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(count, loop); } - assert_int_equal(result, ISC_R_NOMORE); - assert_int_equal(count, loop); break; } case dns_rdatatype_hip: { dns_rdata_hip_t *hip = rdata_struct; - for (result = dns_rdata_hip_first(hip); result == ISC_R_SUCCESS; - result = dns_rdata_hip_next(hip)) - { - dns_name_t name; - dns_name_init(&name, NULL); - dns_rdata_hip_current(hip, &name); - assert_int_not_equal(dns_name_countlabels(&name), 0); - assert_true(dns_name_isabsolute(&name)); - count++; + for (size_t pass = 1; pass < 3; pass++) { + unsigned int count = 0; + for (result = dns_rdata_hip_first(hip); + result == ISC_R_SUCCESS; + result = dns_rdata_hip_next(hip)) + { + dns_name_t name; + dns_name_init(&name, NULL); + dns_rdata_hip_current(hip, &name); + assert_int_not_equal( + dns_name_countlabels(&name), 0); + assert_true(dns_name_isabsolute(&name)); + count++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(count, loop); } - assert_int_equal(result, ISC_R_NOMORE); - assert_int_equal(count, loop); break; } case dns_rdatatype_https: { dns_rdata_in_https_t *https = rdata_struct; - for (result = dns_rdata_in_https_first(https); - result == ISC_R_SUCCESS; - result = dns_rdata_in_https_next(https)) - { - isc_region_t region; - dns_rdata_in_https_current(https, ®ion); - assert_true(region.length >= 4); - count++; + for (size_t pass = 1; pass < 3; pass++) { + unsigned int count = 0; + for (result = dns_rdata_in_https_first(https); + result == ISC_R_SUCCESS; + result = dns_rdata_in_https_next(https)) + { + isc_region_t region; + dns_rdata_in_https_current(https, ®ion); + assert_true(region.length >= 4); + count++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(count, loop); } - assert_int_equal(result, ISC_R_NOMORE); - assert_int_equal(count, loop); break; } case dns_rdatatype_svcb: { dns_rdata_in_svcb_t *svcb = rdata_struct; - for (result = dns_rdata_in_svcb_first(svcb); - result == ISC_R_SUCCESS; - result = dns_rdata_in_svcb_next(svcb)) - { - isc_region_t region; - dns_rdata_in_svcb_current(svcb, ®ion); - assert_true(region.length >= 4); - count++; + for (size_t pass = 1; pass < 3; pass++) { + unsigned int count = 0; + for (result = dns_rdata_in_svcb_first(svcb); + result == ISC_R_SUCCESS; + result = dns_rdata_in_svcb_next(svcb)) + { + isc_region_t region; + dns_rdata_in_svcb_current(svcb, ®ion); + assert_true(region.length >= 4); + count++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(count, loop); } - assert_int_equal(result, ISC_R_NOMORE); - assert_int_equal(count, loop); break; } }