From f555f1d2eb4cf0d0cf2f6aa1203d369aecb7339e Mon Sep 17 00:00:00 2001
From: Aram Sargsyan <aram@isc.org>
Date: Tue, 28 Dec 2021 11:51:01 +0000
Subject: [PATCH] Improve the view configuration error handling and reverting
 logic

If a view configuration error occurs during a named reconfiguration
procedure, BIND can end up having twin views (old and new), with some
zones and internal structures attached to the old one, and others
attached to the new one, which essentially creates chaos.

Implement some additional view reverting mechanisms to avoid the
situation described above:

 1. Revert rpz configuration.

 2. Revert catz configuration.

 3. Revert zones to view attachments.

(cherry picked from commit 3697560f048792430640e7f848cdae8547c70d90)
Conflict: NA
Reference: https://gitlab.isc.org/isc-projects/bind9/-/commit/f555f1d2eb4cf0d0cf2f6aa1203d369aecb7339e
---
 bin/named/server.c | 153 +++++++++++++++++++++++++++++++++++++--------
 1 file changed, 126 insertions(+), 27 deletions(-)

diff --git a/bin/named/server.c b/bin/named/server.c
index d8993e06b4..721464db8e 100644
--- a/bin/named/server.c
+++ b/bin/named/server.c
@@ -437,6 +437,10 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig,
 	       cfg_aclconfctx_t *aclconf, bool added, bool old_rpz_ok,
 	       bool modify);
 
+static void
+configure_zone_setviewcommit(isc_result_t result, const cfg_obj_t *zconfig,
+			     dns_view_t *view);
+
 static isc_result_t
 configure_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig,
 		   isc_mem_t *mctx, cfg_aclconfctx_t *actx);
@@ -2439,7 +2443,7 @@ configure_rpz_zone(dns_view_t *view, const cfg_listelt_t *element,
 }
 
 static isc_result_t
-configure_rpz(dns_view_t *view, const cfg_obj_t **maps,
+configure_rpz(dns_view_t *view, dns_view_t *pview, const cfg_obj_t **maps,
 	      const cfg_obj_t *rpz_obj, bool *old_rpz_okp) {
 	bool dnsrps_enabled;
 	const cfg_listelt_t *zone_element;
@@ -2453,7 +2457,7 @@ configure_rpz(dns_view_t *view, const cfg_obj_t **maps,
 	uint32_t minupdateinterval_default;
 	dns_rpz_zones_t *zones;
 	const dns_rpz_zones_t *old;
-	dns_view_t *pview;
+	bool pview_must_detach = false;
 	const dns_rpz_zone_t *old_zone;
 	isc_result_t result;
 	int i;
@@ -2592,14 +2596,19 @@ configure_rpz(dns_view_t *view, const cfg_obj_t **maps,
 		zones->p.nsip_wait_recurse = false;
 	}
 
-	pview = NULL;
-	result = dns_viewlist_find(&named_g_server->viewlist, view->name,
-				   view->rdclass, &pview);
-	if (result == ISC_R_SUCCESS) {
+	if (pview != NULL) {
 		old = pview->rpzs;
 	} else {
-		old = NULL;
+		result = dns_viewlist_find(&named_g_server->viewlist,
+					   view->name, view->rdclass, &pview);
+		if (result == ISC_R_SUCCESS) {
+			pview_must_detach = true;
+			old = pview->rpzs;
+		} else {
+			old = NULL;
+		}
 	}
+
 	if (old == NULL) {
 		*old_rpz_okp = false;
 	} else {
@@ -2621,7 +2630,7 @@ configure_rpz(dns_view_t *view, const cfg_obj_t **maps,
 			add_soa_default, ttl_default, minupdateinterval_default,
 			old_zone, old_rpz_okp);
 		if (result != ISC_R_SUCCESS) {
-			if (pview != NULL) {
+			if (pview_must_detach) {
 				dns_view_detach(&pview);
 			}
 			return (result);
@@ -2658,7 +2667,7 @@ configure_rpz(dns_view_t *view, const cfg_obj_t **maps,
 			    view->rpzs->rpz_ver);
 	}
 
-	if (pview != NULL) {
+	if (pview_must_detach) {
 		dns_view_detach(&pview);
 	}
 
@@ -2987,15 +2996,14 @@ catz_modzone(dns_catz_entry_t *entry, dns_catz_zone_t *origin, dns_view_t *view,
 }
 
 static isc_result_t
-configure_catz_zone(dns_view_t *view, const cfg_obj_t *config,
-		    const cfg_listelt_t *element) {
+configure_catz_zone(dns_view_t *view, dns_view_t *pview,
+		    const cfg_obj_t *config, const cfg_listelt_t *element) {
 	const cfg_obj_t *catz_obj, *obj;
 	dns_catz_zone_t *zone = NULL;
 	const char *str;
 	isc_result_t result;
 	dns_name_t origin;
 	dns_catz_options_t *opts;
-	dns_view_t *pview = NULL;
 
 	dns_name_init(&origin, NULL);
 	catz_obj = cfg_listelt_value(element);
@@ -3026,9 +3034,7 @@ configure_catz_zone(dns_view_t *view, const cfg_obj_t *config,
 	if (result == ISC_R_EXISTS) {
 		isc_ht_iter_t *it = NULL;
 
-		result = dns_viewlist_find(&named_g_server->viewlist,
-					   view->name, view->rdclass, &pview);
-		RUNTIME_CHECK(result == ISC_R_SUCCESS);
+		RUNTIME_CHECK(pview != NULL);
 
 		/*
 		 * xxxwpk todo: reconfigure the zone!!!!
@@ -3116,9 +3122,6 @@ configure_catz_zone(dns_view_t *view, const cfg_obj_t *config,
 	}
 
 cleanup:
-	if (pview != NULL) {
-		dns_view_detach(&pview);
-	}
 	dns_name_free(&origin, view->mctx);
 
 	return (result);
@@ -3130,11 +3133,11 @@ static dns_catz_zonemodmethods_t ns_catz_zonemodmethods = {
 };
 
 static isc_result_t
-configure_catz(dns_view_t *view, const cfg_obj_t *config,
+configure_catz(dns_view_t *view, dns_view_t *pview, const cfg_obj_t *config,
 	       const cfg_obj_t *catz_obj) {
 	const cfg_listelt_t *zone_element;
 	const dns_catz_zones_t *old = NULL;
-	dns_view_t *pview = NULL;
+	bool pview_must_detach = false;
 	isc_result_t result;
 
 	/* xxxwpk TODO do it cleaner, once, somewhere */
@@ -3149,10 +3152,15 @@ configure_catz(dns_view_t *view, const cfg_obj_t *config,
 				 view->mctx, named_g_taskmgr,
 				 named_g_timermgr));
 
-	result = dns_viewlist_find(&named_g_server->viewlist, view->name,
-				   view->rdclass, &pview);
-	if (result == ISC_R_SUCCESS) {
+	if (pview != NULL) {
 		old = pview->catzs;
+	} else {
+		result = dns_viewlist_find(&named_g_server->viewlist,
+					   view->name, view->rdclass, &pview);
+		if (result == ISC_R_SUCCESS) {
+			pview_must_detach = true;
+			old = pview->catzs;
+		}
 	}
 
 	if (old != NULL) {
@@ -3162,7 +3170,7 @@ configure_catz(dns_view_t *view, const cfg_obj_t *config,
 	}
 
 	while (zone_element != NULL) {
-		CHECK(configure_catz_zone(view, config, zone_element));
+		CHECK(configure_catz_zone(view, pview, config, zone_element));
 		zone_element = cfg_list_next(zone_element);
 	}
 
@@ -3173,7 +3181,7 @@ configure_catz(dns_view_t *view, const cfg_obj_t *config,
 	result = ISC_R_SUCCESS;
 
 cleanup:
-	if (pview != NULL) {
+	if (pview_must_detach) {
 		dns_view_detach(&pview);
 	}
 
@@ -3974,6 +3982,9 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
 	isc_mem_t *cmctx = NULL, *hmctx = NULL;
 	dns_dispatch_t *dispatch4 = NULL;
 	dns_dispatch_t *dispatch6 = NULL;
+	bool rpz_configured = false;
+	bool catz_configured = false;
+	bool zones_configured = false;
 	bool reused_cache = false;
 	bool shared_cache = false;
 	int i = 0, j = 0, k = 0;
@@ -4045,14 +4056,16 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
 	if (view->rdclass == dns_rdataclass_in && need_hints &&
 	    named_config_get(maps, "response-policy", &obj) == ISC_R_SUCCESS)
 	{
-		CHECK(configure_rpz(view, maps, obj, &old_rpz_ok));
+		CHECK(configure_rpz(view, NULL, maps, obj, &old_rpz_ok));
+		rpz_configured = true;
 	}
 
 	obj = NULL;
 	if (view->rdclass == dns_rdataclass_in && need_hints &&
 	    named_config_get(maps, "catalog-zones", &obj) == ISC_R_SUCCESS)
 	{
-		CHECK(configure_catz(view, config, obj));
+		CHECK(configure_catz(view, NULL, config, obj));
+		catz_configured = true;
 	}
 
 	/*
@@ -4076,6 +4089,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
 				     viewlist, kasplist, actx, false,
 				     old_rpz_ok, false));
 	}
+	zones_configured = true;
 
 	/*
 	 * Check that a master or slave zone was found for each
@@ -5815,6 +5829,91 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
 	result = ISC_R_SUCCESS;
 
 cleanup:
+	/*
+	 * Revert to the old view if there was an error.
+	 */
+	if (result != ISC_R_SUCCESS) {
+		isc_result_t result2;
+
+		result2 = dns_viewlist_find(&named_g_server->viewlist,
+					    view->name, view->rdclass, &pview);
+		if (result2 == ISC_R_SUCCESS) {
+			dns_view_thaw(pview);
+
+			obj = NULL;
+			if (rpz_configured &&
+			    pview->rdclass == dns_rdataclass_in && need_hints &&
+			    named_config_get(maps, "response-policy", &obj) ==
+				    ISC_R_SUCCESS)
+			{
+				/*
+				 * We are swapping the places of the `view` and
+				 * `pview` in the function's parameters list
+				 * because we are reverting the same operation
+				 * done previously in the "correct" order.
+				 */
+				result2 = configure_rpz(pview, view, maps, obj,
+							&old_rpz_ok);
+				if (result2 != ISC_R_SUCCESS) {
+					isc_log_write(named_g_lctx,
+						      NAMED_LOGCATEGORY_GENERAL,
+						      NAMED_LOGMODULE_SERVER,
+						      ISC_LOG_ERROR,
+						      "rpz configuration "
+						      "revert failed for view "
+						      "'%s'",
+						      pview->name);
+				}
+			}
+
+			obj = NULL;
+			if (catz_configured &&
+			    pview->rdclass == dns_rdataclass_in && need_hints &&
+			    named_config_get(maps, "catalog-zones", &obj) ==
+				    ISC_R_SUCCESS)
+			{
+				if (pview->catzs != NULL) {
+					dns_catz_catzs_detach(&pview->catzs);
+				}
+				/*
+				 * We are swapping the places of the `view` and
+				 * `pview` in the function's parameters list
+				 * because we are reverting the same operation
+				 * done previously in the "correct" order.
+				 */
+				result2 = configure_catz(pview, view, config,
+							 obj);
+				if (result2 != ISC_R_SUCCESS) {
+					isc_log_write(named_g_lctx,
+						      NAMED_LOGCATEGORY_GENERAL,
+						      NAMED_LOGMODULE_SERVER,
+						      ISC_LOG_ERROR,
+						      "catz configuration "
+						      "revert failed for view "
+						      "'%s'",
+						      pview->name);
+				}
+			}
+
+			dns_view_freeze(pview);
+		}
+
+		if (pview != NULL) {
+			dns_view_detach(&pview);
+		}
+
+		if (zones_configured) {
+			for (element = cfg_list_first(zonelist);
+			     element != NULL; element = cfg_list_next(element))
+			{
+				const cfg_obj_t *zconfig =
+					cfg_listelt_value(element);
+				configure_zone_setviewcommit(result, zconfig,
+							     view);
+			}
+		}
+	}
+
 	if (ntatable != NULL) {
 		dns_ntatable_detach(&ntatable);
 	}
-- 
2.23.0

