From d95a18711bd871651555b7a0a6914229341e0b84 Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Tue, 22 Sep 2020 15:22:34 +1000
Subject: [PATCH] Break lock order loop by sending TAT in an event

The dotat() function has been changed to send the TAT
query asynchronously, so there's no lock order loop
because we initialize the data first and then we schedule
the TAT send to happen asynchronously.

This breaks following lock-order loops:

zone->lock (dns_zone_setviewcommit) while holding view->lock
(dns_view_setviewcommit)

keytable->lock (dns_keytable_find) while holding zone->lock
(zone_asyncload)

view->lock (dns_view_findzonecut) while holding keytable->lock
(dns_keytable_forall)

(cherry picked from commit 3c4b68af7c0cd8213bcae92faee3bf2a7e9284d1)
Conflict: NA
Reference: https://gitlab.isc.org/isc-projects/bind9/-/commit/d95a18711bd871651555b7a0a6914229341e0b84
---
 bin/named/include/named/server.h |   1 +
 bin/named/server.c               | 139 ++++++++++++++++++++-----------
 2 files changed, 93 insertions(+), 47 deletions(-)

diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h
index b2537e7aa0..4fd0194a54 100644
--- a/bin/named/include/named/server.h
+++ b/bin/named/include/named/server.h
@@ -34,6 +34,7 @@
 #define NS_EVENT_RELOAD		(NS_EVENTCLASS + 0)
 #define NS_EVENT_CLIENTCONTROL	(NS_EVENTCLASS + 1)
 #define NS_EVENT_DELZONE	(NS_EVENTCLASS + 2)
+#define NS_EVENT_TATSEND	(NS_EVENTCLASS + 3)
 
 /*%
  * Name server state.  Better here than in lots of separate global variables.
diff --git a/bin/named/server.c b/bin/named/server.c
index 638625f171..30d38beb40 100644
--- a/bin/named/server.c
+++ b/bin/named/server.c
@@ -6094,11 +6094,14 @@ heartbeat_timer_tick(isc_task_t *task, isc_event_t *event) {
 }
 
 typedef struct {
-       isc_mem_t       *mctx;
-       isc_task_t      *task;
-       dns_rdataset_t  rdataset;
-       dns_rdataset_t  sigrdataset;
-       dns_fetch_t     *fetch;
+	isc_mem_t *mctx;
+	isc_task_t *task;
+	dns_fetch_t *fetch;
+	dns_view_t *view;
+	dns_fixedname_t tatname;
+	dns_fixedname_t keyname;
+	dns_rdataset_t rdataset;
+	dns_rdataset_t sigrdataset;
 } ns_tat_t;
 
 static int
@@ -6118,10 +6121,11 @@ tat_done(isc_task_t *task, isc_event_t *event) {
 	dns_fetchevent_t *devent;
 	ns_tat_t *tat;
 
-	UNUSED(task);
 	INSIST(event != NULL && event->ev_type == DNS_EVENT_FETCHDONE);
 	INSIST(event->ev_arg != NULL);
 
+	UNUSED(task);
+
 	tat = event->ev_arg;
 	devent = (dns_fetchevent_t *) event;
 
@@ -6136,6 +6140,7 @@ tat_done(isc_task_t *task, isc_event_t *event) {
 		dns_rdataset_disassociate(&tat->rdataset);
 	if (dns_rdataset_isassociated(&tat->sigrdataset))
 		dns_rdataset_disassociate(&tat->sigrdataset);
+	dns_view_detach(&tat->view);
 	isc_task_detach(&tat->task);
 	isc_mem_putanddetach(&tat->mctx, tat, sizeof(*tat));
 }
@@ -6148,7 +6153,7 @@ struct dotat_arg {
 /*%
  * Prepare the QNAME for the TAT query to be sent by processing the trust
  * anchors present at 'keynode' of 'keytable'.  Store the result in 'dst' and
- * the domain name which 'keynode' is associated with in 'origin'.
+ * the domain name which 'keynode' is associated with in 'keyname'.
  *
  * A maximum of 12 key IDs can be reported in a single TAT query due to the
  * 63-octet length limit for any single label in a domain name.  If there are
@@ -6156,7 +6161,7 @@ struct dotat_arg {
  * reported in the TAT query.
  */
 static isc_result_t
-get_tat_qname(dns_name_t *dst, dns_name_t **origin, dns_keytable_t *keytable,
+get_tat_qname(dns_name_t *dst, dns_name_t **keyname, dns_keytable_t *keytable,
 	      dns_keynode_t *keynode)
 {
 	dns_keynode_t *firstnode = keynode;
@@ -6167,12 +6172,12 @@ get_tat_qname(dns_name_t *dst, dns_name_t **origin, dns_keytable_t *keytable,
 	char label[64];
 	int m;
 
-	REQUIRE(origin != NULL && *origin == NULL);
+	REQUIRE(keyname != NULL && *keyname == NULL);
 
 	do {
 		dst_key_t *key = dns_keynode_key(keynode);
 		if (key != NULL) {
-			*origin = dst_key_name(key);
+			*keyname = dst_key_name(key);
 			if (n < (sizeof(ids)/sizeof(ids[0]))) {
 				ids[n] = dst_key_id(key);
 				n++;
@@ -6212,52 +6217,35 @@ get_tat_qname(dns_name_t *dst, dns_name_t **origin, dns_keytable_t *keytable,
 		isc_textregion_consume(&r, m);
 	}
 
-	return (dns_name_fromstring2(dst, label, *origin, 0, NULL));
+	return (dns_name_fromstring2(dst, label, *keyname, 0, NULL));
 }
 
 static void
-dotat(dns_keytable_t *keytable, dns_keynode_t *keynode, void *arg) {
-	struct dotat_arg *dotat_arg = arg;
+tat_send(isc_task_t *task, isc_event_t *event) {
+	ns_tat_t *tat;
 	char namebuf[DNS_NAME_FORMATSIZE];
-	dns_fixedname_t fixed, fdomain;
-	dns_name_t *tatname, *domain;
+	dns_fixedname_t fdomain;
+	dns_name_t *domain;
 	dns_rdataset_t nameservers;
-	dns_name_t *origin = NULL;
 	isc_result_t result;
-	dns_view_t *view;
-	isc_task_t *task;
-	ns_tat_t *tat;
+	dns_name_t *keyname;
+	dns_name_t *tatname;
 
-	REQUIRE(keytable != NULL);
-	REQUIRE(keynode != NULL);
-	REQUIRE(dotat_arg != NULL);
+	INSIST(event != NULL && event->ev_type == NS_EVENT_TATSEND);
+	INSIST(event->ev_arg != NULL);
 
-	view = dotat_arg->view;
-	task = dotat_arg->task;
+	UNUSED(task);
 
-	tatname = dns_fixedname_initname(&fixed);
-	result = get_tat_qname(tatname, &origin, keytable, keynode);
-	if (result != ISC_R_SUCCESS) {
-		return;
-	}
+	tat = event->ev_arg;
+
+	tatname = dns_fixedname_name(&tat->tatname);
+	keyname = dns_fixedname_name(&tat->keyname);
 
 	dns_name_format(tatname, namebuf, sizeof(namebuf));
 	isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER,
 		      ISC_LOG_INFO,
-		     "%s: sending trust-anchor-telemetry query '%s/NULL'",
-		     view->name, namebuf);
-
-	tat = isc_mem_get(dotat_arg->view->mctx, sizeof(*tat));
-	if (tat == NULL)
-		return;
-
-	tat->mctx = NULL;
-	tat->task = NULL;
-	tat->fetch = NULL;
-	dns_rdataset_init(&tat->rdataset);
-	dns_rdataset_init(&tat->sigrdataset);
-	isc_mem_attach(dotat_arg->view->mctx, &tat->mctx);
-	isc_task_attach(task, &tat->task);
+		      "%s: sending trust-anchor-telemetry query '%s/NULL'",
+		      tat->view->name, namebuf);
 
 	/*
 	 * TAT queries should be sent to the authoritative servers for a given
@@ -6276,20 +6264,20 @@ dotat(dns_keytable_t *keytable, dns_keynode_t *keynode, void *arg) {
 	 * order to eventually find the destination host to send the TAT query
 	 * to.
 	 *
-	 * 'origin' holds the domain name at 'keynode', i.e. the domain name
+	 * 'keyname' holds the domain name at 'keynode', i.e. the domain name
 	 * for which the trust anchors to be reported by this TAT query are
 	 * defined.
 	 *
 	 * After the dns_view_findzonecut() call, 'domain' will hold the
-	 * deepest zone cut we can find for 'origin' while 'nameservers' will
+	 * deepest zone cut we can find for 'keyname' while 'nameservers' will
 	 * hold the NS RRset at that zone cut.
 	 */
 	domain = dns_fixedname_initname(&fdomain);
 	dns_rdataset_init(&nameservers);
-	result = dns_view_findzonecut(view, origin, domain, 0, 0, true,
+	result = dns_view_findzonecut(tat->view, keyname, domain, 0, 0, true,
 				      &nameservers, NULL);
 	if (result == ISC_R_SUCCESS) {
-		result = dns_resolver_createfetch(view->resolver, tatname,
+		result = dns_resolver_createfetch(tat->view->resolver, tatname,
 						  dns_rdatatype_null, domain,
 						  &nameservers, NULL, 0,
 						  tat->task, tat_done, tat,
@@ -6314,9 +6302,66 @@ dotat(dns_keytable_t *keytable, dns_keynode_t *keynode, void *arg) {
 	}
 
 	if (result != ISC_R_SUCCESS) {
+		dns_view_detach(&tat->view);
 		isc_task_detach(&tat->task);
 		isc_mem_putanddetach(&tat->mctx, tat, sizeof(*tat));
 	}
+	isc_event_free(&event);
+}
+
+static void
+dotat(dns_keytable_t *keytable, dns_keynode_t *keynode, void *arg) {
+	struct dotat_arg *dotat_arg = arg;
+	dns_name_t *keyname = NULL;
+	isc_result_t result;
+	dns_view_t *view;
+	isc_task_t *task;
+	ns_tat_t *tat;
+	isc_event_t *event;
+
+	REQUIRE(keytable != NULL);
+	REQUIRE(keynode != NULL);
+	REQUIRE(dotat_arg != NULL);
+
+	view = dotat_arg->view;
+	task = dotat_arg->task;
+
+	tat = isc_mem_get(dotat_arg->view->mctx, sizeof(*tat));
+
+	tat->fetch = NULL;
+	tat->mctx = NULL;
+	tat->task = NULL;
+	tat->view = NULL;
+	dns_rdataset_init(&tat->rdataset);
+	dns_rdataset_init(&tat->sigrdataset);
+	result = get_tat_qname(dns_fixedname_initname(&tat->tatname), &keyname,
+			       keytable, keynode);
+	if (result != ISC_R_SUCCESS) {
+		isc_mem_put(dotat_arg->view->mctx, tat, sizeof(*tat));
+		return;
+	}
+	dns_name_copy(keyname, dns_fixedname_initname(&tat->keyname), NULL);
+	isc_mem_attach(dotat_arg->view->mctx, &tat->mctx);
+	isc_task_attach(task, &tat->task);
+	dns_view_attach(view, &tat->view);
+
+	/*
+	 * We don't want to be holding the keytable lock when calling
+	 * dns_view_findzonecut() as it creates a lock order loop so
+	 * call dns_view_findzonecut() in a event handler.
+	 *
+	 * zone->lock (dns_zone_setviewcommit) while holding view->lock
+	 * (dns_view_setviewcommit)
+	 *
+	 * keytable->lock (dns_keytable_find) while holding zone->lock
+	 * (zone_asyncload)
+	 *
+	 * view->lock (dns_view_findzonecut) while holding keytable->lock
+	 * (dns_keytable_forall)
+	 */
+	event = isc_event_allocate(tat->mctx, keytable, NS_EVENT_TATSEND,
+				   tat_send, tat, sizeof(isc_event_t));
+	isc_task_send(task, &event);
 }
 
 static void
-- 
2.23.0

