Commit 4e8f1445a2 for bind
commit 4e8f1445a281208a15cfd1f50bd31fbc9968cc55
Author: OndÅ™ej Surý <ondrej@isc.org>
Date: Thu Aug 13 09:08:53 2026 +0200
Reject duplicate singleton records
Skipping identical repeats keeps the parser tolerant enough for the
maintenance branches, but a second SOA, CNAME or DNAME record for the
same owner has no legitimate use, whether or not its RDATA matches the
first one. Treat any repeat as a malformed message so that a crafted
response fails as early as possible instead of being carried further
into the resolver. The chain system test relied on repeated CNAMEs being
accepted and is adjusted accordingly.
diff --git a/bin/tests/system/chain/tests.sh b/bin/tests/system/chain/tests.sh
index e19052ad11..2917049afa 100644
--- a/bin/tests/system/chain/tests.sh
+++ b/bin/tests/system/chain/tests.sh
@@ -511,7 +511,7 @@ grep 'status: NOERROR' dig.out.1.$n >/dev/null 2>&1 || ret=1
grep 'ANSWER: 2' dig.out.1.$n >/dev/null 2>&1 || ret=1
$RNDCCMD 10.53.0.7 null --- start test$n - step 2 --- 2>&1 | sed 's/^/ns7 /' | cat_i
$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i
-sendcmd 10.53.0.4 setup-chain "cname.cname.cname._.1.1.2.2.3.4.s4.s3.s1"
+sendcmd 10.53.0.4 setup-chain "cname.cname.cname._.1.2.3.4.s4.s3.s1"
$DIG $DIGOPTS @10.53.0.7 test.domain.nil >dig.out.2.$n 2>&1
grep 'status: NOERROR' dig.out.2.$n >/dev/null 2>&1 || ret=1
grep 'ANSWER: 2' dig.out.2.$n >/dev/null 2>&1 || ret=1
@@ -535,7 +535,7 @@ grep 'status: NOERROR' dig.out.5.$n >/dev/null 2>&1 || ret=1
grep 'ANSWER: 2' dig.out.5.$n >/dev/null 2>&1 || ret=1
$RNDCCMD 10.53.0.7 null --- start test$n - step 6 --- 2>&1 | sed 's/^/ns7 /' | cat_i
$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i
-sendcmd 10.53.0.4 setup-chain "cname.cname.cname._.4.3.3.3.s1.s1.1.3.4"
+sendcmd 10.53.0.4 setup-chain "cname.cname.cname._.4.3.s1.s1.1"
$DIG $DIGOPTS @10.53.0.7 test.domain.nil >dig.out.6.$n 2>&1
grep 'status: NOERROR' dig.out.6.$n >/dev/null 2>&1 || ret=1
grep 'ANSWER: 2' dig.out.6.$n >/dev/null 2>&1 || ret=1
diff --git a/lib/dns/message.c b/lib/dns/message.c
index d34bf43133..7b7af72769 100644
--- a/lib/dns/message.c
+++ b/lib/dns/message.c
@@ -1428,21 +1428,13 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t dctx,
result = ISC_R_SUCCESS;
rdataset = found_rdataset;
- if (!dns_rdatatype_issingleton(rdtype)) {
- break;
- }
-
- dns_rdatalist_fromrdataset(rdataset,
- &rdatalist);
- dns_rdata_t *first =
- ISC_LIST_HEAD(rdatalist->rdata);
- INSIST(first != NULL);
- if (dns_rdata_compare(rdata, first) != 0) {
+ if (dns_rdatatype_issingleton(rdtype)) {
+ if (!best_effort) {
+ dns_message_puttemprdata(
+ msg, &rdata);
+ }
DO_ERROR(DNS_R_FORMERR);
}
- if (!best_effort) {
- dns_message_puttemprdata(msg, &rdata);
- }
break;
case ISC_R_SUCCESS:
ISC_LIST_APPEND(name->list, rdataset, link);
diff --git a/tests/dns/message_test.c b/tests/dns/message_test.c
index 53d79d9b04..880f8cde46 100644
--- a/tests/dns/message_test.c
+++ b/tests/dns/message_test.c
@@ -128,8 +128,10 @@ get_rdataset(dns_message_t *msg, dns_section_t section, const char *owner,
}
/*
- * A record of a singleton type repeated with identical RDATA is kept once;
- * repeats of other types are all retained, as before.
+ * A record of a singleton type listed twice is a malformed message even
+ * when the RDATA is identical; repeats of other types are all retained,
+ * as before. With best-effort parsing the problem is reported, but both
+ * singleton records are retained, keeping the message usable for inspection.
*/
ISC_RUN_TEST_IMPL(parse_duplicate_singleton) {
unsigned char wirebuf[1024];
@@ -155,12 +157,17 @@ ISC_RUN_TEST_IMPL(parse_duplicate_singleton) {
"ns.example. hostmaster.example. 1 3600 600 86400 300");
result = parse(&wire, 0, &msg);
- assert_int_equal(result, ISC_R_SUCCESS);
+ assert_int_equal(result, DNS_R_FORMERR);
+ dns_message_detach(&msg);
+
+ isc_buffer_first(&wire);
+ result = parse(&wire, DNS_MESSAGEPARSE_BESTEFFORT, &msg);
+ assert_int_equal(result, DNS_R_RECOVERABLE);
rdataset = get_rdataset(msg, DNS_SECTION_ANSWER, "dup.example.",
dns_rdatatype_cname);
assert_non_null(rdataset);
- assert_int_equal(dns_rdataset_count(rdataset), 1);
+ assert_int_equal(dns_rdataset_count(rdataset), 2);
assert_int_equal(rdataset->ttl, 300);
rdataset = get_rdataset(msg, DNS_SECTION_ANSWER, "target.example.",
@@ -172,14 +179,14 @@ ISC_RUN_TEST_IMPL(parse_duplicate_singleton) {
rdataset = get_rdataset(msg, DNS_SECTION_AUTHORITY, "example.",
dns_rdatatype_soa);
assert_non_null(rdataset);
- assert_int_equal(dns_rdataset_count(rdataset), 1);
+ assert_int_equal(dns_rdataset_count(rdataset), 2);
assert_int_equal(rdataset->ttl, 0);
dns_message_detach(&msg);
}
/*
- * A singleton type with two different RDATA is still a malformed message.
+ * A singleton type with two different RDATA is a malformed message too.
*/
ISC_RUN_TEST_IMPL(parse_conflicting_singleton) {
unsigned char wirebuf[1024];
@@ -200,7 +207,7 @@ ISC_RUN_TEST_IMPL(parse_conflicting_singleton) {
/*
* With best-effort parsing the problem is reported but the message
- * is still usable, and only the first CNAME survives.
+ * is still usable, and both CNAMEs are retained.
*/
isc_buffer_first(&wire);
result = parse(&wire, DNS_MESSAGEPARSE_BESTEFFORT, &msg);