Commit bb8c6d045a for bind

commit bb8c6d045a4e4a5d7f2900b8b1d8f7a747e92fae
Author: Alessio Podda <alessio@isc.org>
Date:   Mon Aug 10 15:37:26 2026 +0200

    Sort unscheduled headers last in the resign heap

    Treat headers without the RESIGN attribute as having an effective
    resign time of INT64_MAX. This prevents defensive heap entries from
    affecting the next scheduled resign time.

diff --git a/lib/dns/qpzone.c b/lib/dns/qpzone.c
index d30add29fc..488ea5a964 100644
--- a/lib/dns/qpzone.c
+++ b/lib/dns/qpzone.c
@@ -657,9 +657,15 @@ resign_sooner_values(int64_t lhs_resign, dns_typepair_t lhs_typepair,
 	       (lhs_resign == rhs_resign && lhs_is_soa < rhs_is_soa);
 }

+static int64_t
+resign_effective_time(const dns_vecheader_t *header) {
+	return RESIGN(header) ? header->resign : INT64_MAX;
+}
+
 /*%
  * Return which RRset should be resigned sooner.  If the RRsets have the
- * same signing time, prefer the other RRset over the SOA RRset.
+ * same signing time, prefer the other RRset over the SOA RRset.  Headers
+ * which are not scheduled for resigning sort after every scheduled header.
  */
 static bool
 resign_sooner(void *v1, void *v2) {
@@ -667,8 +673,8 @@ resign_sooner(void *v1, void *v2) {
 	qpz_resign_t *elem2 = v2;

 	return resign_sooner_values(
-		elem1->header->resign, elem1->header->typepair,
-		elem2->header->resign, elem2->header->typepair);
+		resign_effective_time(elem1->header), elem1->header->typepair,
+		resign_effective_time(elem2->header), elem2->header->typepair);
 }

 /*%
@@ -2562,9 +2568,9 @@ again:
 		goto again;
 	}

-	if (elem != NULL) {
+	if (elem != NULL && RESIGN(elem->header)) {
 		header = elem->header;
-		*resign = RESIGN(header) ? (uint32_t)header->resign : 0;
+		*resign = (uint32_t)header->resign;
 		dns_name_copy(&elem->node->name, foundname);
 		*typepair = header->typepair;
 		result = ISC_R_SUCCESS;
diff --git a/tests/dns/qpzone_test.c b/tests/dns/qpzone_test.c
index 5313bab0a2..56a3f44fec 100644
--- a/tests/dns/qpzone_test.c
+++ b/tests/dns/qpzone_test.c
@@ -390,6 +390,17 @@ ISC_RUN_TEST_IMPL(setownercase) {
 ISC_RUN_TEST_IMPL(resign_sooner_values) {
 	dns_typepair_t soa = DNS_SIGTYPEPAIR(dns_rdatatype_soa);
 	dns_typepair_t other = DNS_SIGTYPEPAIR(dns_rdatatype_a);
+	dns_vecheader_t scheduled = {
+		.attributes = DNS_VECHEADERATTR_RESIGN,
+		.typepair = other,
+		.resign = 20,
+	};
+	dns_vecheader_t unscheduled = {
+		.typepair = other,
+		.resign = 0,
+	};
+	qpz_resign_t scheduled_elem = { .header = &scheduled };
+	qpz_resign_t unscheduled_elem = { .header = &unscheduled };

 	UNUSED(state);

@@ -401,6 +412,58 @@ ISC_RUN_TEST_IMPL(resign_sooner_values) {

 	assert_false(resign_sooner_values(10, soa, 10, soa));
 	assert_false(resign_sooner_values(10, other, 10, other));
+
+	assert_true(resign_sooner(&scheduled_elem, &unscheduled_elem));
+	assert_false(resign_sooner(&unscheduled_elem, &scheduled_elem));
+}
+
+ISC_RUN_TEST_IMPL(unscheduled_resign) {
+	isc_result_t result;
+	dns_db_t *db = NULL;
+	dns_fixedname_t fixed;
+	dns_typepair_t typepair;
+	isc_stdtime_t resign;
+
+	result = dns__qpzone_create(isc_g_mctx, &example_org_name,
+				    dns_dbtype_zone, dns_rdataclass_in, 0, NULL,
+				    NULL, &db);
+	assert_int_equal(result, ISC_R_SUCCESS);
+
+	WITH_NEWVERSION(db, version, true) {
+		result = apply_dns_update(db, version, &example_org_name,
+					  dns_rdatatype_aaaa, dns_rdataclass_in,
+					  300, aaaa_test_data[0], 16,
+					  DNS_DIFFOP_ADD);
+		assert_int_equal(result, ISC_R_SUCCESS);
+	}
+
+	WITH_NEWVERSION(db, version, true) {
+		result = apply_dns_update(db, version, &example_org_name,
+					  dns_rdatatype_aaaa, dns_rdataclass_in,
+					  300, aaaa_test_data[1], 16,
+					  DNS_DIFFOP_ADD);
+		assert_int_equal(result, ISC_R_SUCCESS);
+	}
+
+	/*
+	 * Rolling back the subtraction currently leaves the old, ordinary
+	 * AAAA header in the resigning heap.  It must not be exposed as a
+	 * scheduled header with resign time zero.
+	 */
+	WITH_NEWVERSION(db, version, false) {
+		result = apply_dns_update(db, version, &example_org_name,
+					  dns_rdatatype_aaaa, dns_rdataclass_in,
+					  300, aaaa_test_data[0], 16,
+					  DNS_DIFFOP_DEL);
+		assert_int_equal(result, ISC_R_SUCCESS);
+	}
+
+	dns_fixedname_init(&fixed);
+	result = dns_db_getsigningtime(db, &resign, dns_fixedname_name(&fixed),
+				       &typepair);
+	assert_int_equal(result, ISC_R_NOTFOUND);
+
+	dns_db_detach(&db);
 }

 ISC_RUN_TEST_IMPL(diffop_add_sub) {
@@ -806,6 +869,7 @@ ISC_TEST_LIST_START
 ISC_TEST_ENTRY(ownercase)
 ISC_TEST_ENTRY(setownercase)
 ISC_TEST_ENTRY(resign_sooner_values)
+ISC_TEST_ENTRY(unscheduled_resign)
 ISC_TEST_ENTRY(diffop_add_sub)
 ISC_TEST_ENTRY(wildcard_foundname)
 ISC_TEST_ENTRY(wildcard_delegation_foundname)