Commit 05b63a45d9 for bind
commit 05b63a45d91cb4bf3ac5b8cf6d6fe323e8863d57
Author: Alessio Podda <alessio@isc.org>
Date: Mon Aug 10 16:10:33 2026 +0200
Restore only previously scheduled headers on rollback
Record a header for rollback only when it was successfully removed
from the resign heap. This prevents rollback from inserting ordinary,
unscheduled headers into the heap.
diff --git a/lib/dns/qpzone.c b/lib/dns/qpzone.c
index 488ea5a964..8d38dd3b4f 100644
--- a/lib/dns/qpzone.c
+++ b/lib/dns/qpzone.c
@@ -747,12 +747,14 @@ resign_register(qpz_heap_t *heap, qpznode_t *node, dns_vecheader_t *header) {
/*%
* Remove an element from the heap/hashmap.
+ * Returns ISC_R_SUCCESS if the element was removed, or ISC_R_NOTFOUND if it
+ * was not registered.
* Assumes heap lock is already held.
*/
-static void
+static isc_result_t
resign_unregister(qpz_heap_t *heap, qpznode_t *node, dns_vecheader_t *header) {
if (header == NULL) {
- return;
+ return ISC_R_NOTFOUND;
}
qpz_resign_t *found_elem = NULL;
@@ -765,12 +767,15 @@ resign_unregister(qpz_heap_t *heap, qpznode_t *node, dns_vecheader_t *header) {
resign_match, &search_elem,
(void **)&found_elem);
- if (result == ISC_R_SUCCESS) {
- isc_heap_delete(heap->heap, found_elem->heap_index);
- isc_hashmap_delete(heap->hashmap, hashval, resign_match,
- found_elem);
- qpz_resign_destroy(heap->mctx, &found_elem);
+ if (result != ISC_R_SUCCESS) {
+ return result;
}
+
+ isc_heap_delete(heap->heap, found_elem->heap_index);
+ isc_hashmap_delete(heap->hashmap, hashval, resign_match, found_elem);
+ qpz_resign_destroy(heap->mctx, &found_elem);
+
+ return ISC_R_SUCCESS;
}
static qpz_heap_t *
@@ -1028,7 +1033,7 @@ clean_multiple_headers(qpz_heap_t *heap, qpznode_t *node, dns_vectop_t *top) {
if (header->serial == parent_serial || IGNORE(header)) {
ISC_SLIST_PTR_REMOVE(p, header, next_header);
LOCK(&heap->lock);
- resign_unregister(heap, node, header);
+ (void)resign_unregister(heap, node, header);
UNLOCK(&heap->lock);
dns_vecheader_unref(header);
} else {
@@ -1055,7 +1060,7 @@ clean_multiple_versions(qpz_heap_t *heap, qpznode_t *node, dns_vectop_t *top,
if (header->serial < least_serial) {
ISC_SLIST_PTR_REMOVE(p, header, next_header);
LOCK(&heap->lock);
- resign_unregister(heap, node, header);
+ (void)resign_unregister(heap, node, header);
UNLOCK(&heap->lock);
dns_vecheader_unref(header);
} else {
@@ -2050,17 +2055,23 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
nodename->length);
LOCK(&qpdb->heap->lock);
- resign_unregister(qpdb->heap, node, header);
+ (void)resign_unregister(qpdb->heap, node, header);
UNLOCK(&qpdb->heap->lock);
dns_vecheader_unref(header);
} else {
if (RESIGN(newheader)) {
+ isc_result_t unregister_result;
+
LOCK(&qpdb->heap->lock);
resign_register(qpdb->heap, node, newheader);
- resign_unregister(qpdb->heap, node, header);
+ unregister_result = resign_unregister(
+ qpdb->heap, node, header);
UNLOCK(&qpdb->heap->lock);
- resign_rollback(qpdb, node, version,
+ if (unregister_result == ISC_R_SUCCESS) {
+ resign_rollback(
+ qpdb, node, version,
header DNS__DB_FLARG_PASS);
+ }
}
ISC_SLIST_PREPEND(foundtop->headers, newheader,
@@ -2086,12 +2097,17 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
}
if (RESIGN(newheader)) {
+ isc_result_t unregister_result;
+
LOCK(&qpdb->heap->lock);
resign_register(qpdb->heap, node, newheader);
- resign_unregister(qpdb->heap, node, header);
+ unregister_result = resign_unregister(qpdb->heap, node,
+ header);
UNLOCK(&qpdb->heap->lock);
- resign_rollback(qpdb, node, version,
- header DNS__DB_FLARG_PASS);
+ if (unregister_result == ISC_R_SUCCESS) {
+ resign_rollback(qpdb, node, version,
+ header DNS__DB_FLARG_PASS);
+ }
}
if (foundtop != NULL) {
@@ -2121,7 +2137,8 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
ntypes >= qpdb->maxtypepername)
{
LOCK(&qpdb->heap->lock);
- resign_unregister(qpdb->heap, node, newheader);
+ (void)resign_unregister(qpdb->heap, node,
+ newheader);
UNLOCK(&qpdb->heap->lock);
dns_vecheader_unref(newheader);
return DNS_R_TOOMANYRECORDS;
@@ -2494,7 +2511,7 @@ setsigningtime(dns_db_t *db, dns_dbnode_t *dbnode, dns_rdataset_t *rdataset,
/* Element is in heap */
INSIST(RESIGN(header));
if (resign == 0) {
- resign_unregister(qpdb->heap, node, header);
+ (void)resign_unregister(qpdb->heap, node, header);
} else {
int64_t old_resign = header->resign;
int64_t new_resign = dns_time64_from32(resign);
@@ -5130,7 +5147,7 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode,
}
if (result == ISC_R_SUCCESS) {
LOCK(&qpdb->heap->lock);
- resign_unregister(qpdb->heap, node, newheader);
+ (void)resign_unregister(qpdb->heap, node, newheader);
UNLOCK(&qpdb->heap->lock);
dns_vecheader_unref(newheader);
newheader = subresult;
@@ -5165,7 +5182,7 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode,
* add a nonexistent header instead.
*/
LOCK(&qpdb->heap->lock);
- resign_unregister(qpdb->heap, node, newheader);
+ (void)resign_unregister(qpdb->heap, node, newheader);
UNLOCK(&qpdb->heap->lock);
dns_vecheader_unref(newheader);
newheader = dns_vecheader_new(db->mctx);
@@ -5176,7 +5193,7 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode,
newheader->serial = version->serial;
} else {
LOCK(&qpdb->heap->lock);
- resign_unregister(qpdb->heap, node, newheader);
+ (void)resign_unregister(qpdb->heap, node, newheader);
UNLOCK(&qpdb->heap->lock);
dns_vecheader_unref(newheader);
goto unlock;
@@ -5192,17 +5209,22 @@ qpzone_subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode,
node->dirty = true;
changed->dirty = true;
+ isc_result_t unregister_result;
+
LOCK(&qpdb->heap->lock);
- resign_unregister(qpdb->heap, node, header);
+ unregister_result = resign_unregister(qpdb->heap, node, header);
UNLOCK(&qpdb->heap->lock);
- resign_rollback(qpdb, node, version, header DNS__DB_FLARG_PASS);
+ if (unregister_result == ISC_R_SUCCESS) {
+ resign_rollback(qpdb, node, version,
+ header DNS__DB_FLARG_PASS);
+ }
} else {
/*
* The rdataset doesn't exist, so we don't need to do anything
* to satisfy the deletion request.
*/
LOCK(&qpdb->heap->lock);
- resign_unregister(qpdb->heap, node, newheader);
+ (void)resign_unregister(qpdb->heap, node, newheader);
UNLOCK(&qpdb->heap->lock);
dns_vecheader_unref(newheader);
if ((options & DNS_DBSUB_EXACT) != 0) {
diff --git a/tests/dns/qpzone_test.c b/tests/dns/qpzone_test.c
index 56a3f44fec..89d273a94f 100644
--- a/tests/dns/qpzone_test.c
+++ b/tests/dns/qpzone_test.c
@@ -420,7 +420,9 @@ ISC_RUN_TEST_IMPL(resign_sooner_values) {
ISC_RUN_TEST_IMPL(unscheduled_resign) {
isc_result_t result;
dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
dns_fixedname_t fixed;
+ dns_rdataset_t rdataset;
dns_typepair_t typepair;
isc_stdtime_t resign;
@@ -446,9 +448,41 @@ ISC_RUN_TEST_IMPL(unscheduled_resign) {
}
/*
- * 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.
+ * Deliberately insert an ordinary header into the resigning heap to
+ * verify that the defensive checks do not expose it as scheduled.
+ */
+ result = dns_db_findnode(db, &example_org_name, false, &node);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_aaaa, 0, 0,
+ &rdataset, NULL);
+ assert_int_equal(result, ISC_R_SUCCESS);
+
+ qpzonedb_t *qpdb = (qpzonedb_t *)db;
+ dns_vecheader_t *header = dns_vecheader_getheader(&rdataset);
+ LOCK(&qpdb->heap->lock);
+ resign_register(qpdb->heap, (qpznode_t *)node, header);
+ UNLOCK(&qpdb->heap->lock);
+
+ dns_fixedname_init(&fixed);
+ result = dns_db_getsigningtime(db, &resign, dns_fixedname_name(&fixed),
+ &typepair);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+
+ LOCK(&qpdb->heap->lock);
+ result = resign_unregister(qpdb->heap, (qpznode_t *)node, header);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ result = resign_unregister(qpdb->heap, (qpznode_t *)node, header);
+ assert_int_equal(result, ISC_R_NOTFOUND);
+ UNLOCK(&qpdb->heap->lock);
+
+ dns_rdataset_disassociate(&rdataset);
+ dns_db_detachnode(&node);
+
+ /*
+ * A rolled-back subtraction must not register an ordinary header that
+ * was not in the resigning heap before the transaction.
*/
WITH_NEWVERSION(db, version, false) {
result = apply_dns_update(db, version, &example_org_name,
@@ -458,7 +492,10 @@ ISC_RUN_TEST_IMPL(unscheduled_resign) {
assert_int_equal(result, ISC_R_SUCCESS);
}
- dns_fixedname_init(&fixed);
+ LOCK(&qpdb->heap->lock);
+ assert_null(isc_heap_element(qpdb->heap->heap, 1));
+ UNLOCK(&qpdb->heap->lock);
+
result = dns_db_getsigningtime(db, &resign, dns_fixedname_name(&fixed),
&typepair);
assert_int_equal(result, ISC_R_NOTFOUND);