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);