07bac3
From c8292710c84e40eeaea6a51df2842fc5f34458d5 Mon Sep 17 00:00:00 2001
07bac3
From: Petr Mensik <pemensik@redhat.com>
07bac3
Date: Wed, 5 Jun 2019 15:57:14 +0200
07bac3
Subject: [PATCH] Fix CVE-2018-5743
07bac3
MIME-Version: 1.0
07bac3
Content-Type: text/plain; charset=UTF-8
07bac3
Content-Transfer-Encoding: 8bit
07bac3
07bac3
Squashed commit of the following:
07bac3
07bac3
commit f8e5335ea483c72341093f02d964b1ea9290077d
07bac3
Author: Petr Mensik <pemensik@redhat.com>
07bac3
Date:   Tue Jun 4 17:19:20 2019 +0200
07bac3
07bac3
    Remove pipelined parameter and tcpconn structure with reference counter
07bac3
07bac3
    It is not ever used in this version. Is not required to work.
07bac3
07bac3
    TCP connection is never shared in this version. Do not use reference
07bac3
    counter, just attach and detach tcpquota to given client.
07bac3
07bac3
commit e3521d5531054b51e897e5458b09392cc752797d
07bac3
Author: Evan Hunt <each@isc.org>
07bac3
Date:   Fri Apr 5 16:26:19 2019 -0700
07bac3
07bac3
    restore allowance for tcp-clients < interfaces
07bac3
07bac3
    in the "refactor tcpquota and pipeline refs" commit, the counting
07bac3
    of active interfaces was tightened in such a way that named could
07bac3
    fail to listen on an interface if there were more interfaces than
07bac3
    tcp-clients. when checking the quota to start accepting on an
07bac3
    interface, if the number of active clients was above zero, then
07bac3
    it was presumed that some other client was able to handle accepting
07bac3
    new connections. this, however, ignored the fact that the current client
07bac3
    could be included in that count, so if the quota was already exceeded
07bac3
    before all the interfaces were listening, some interfaces would never
07bac3
    listen.
07bac3
07bac3
    we now check whether the current client has been marked active; if so,
07bac3
    then the number of active clients on the interface must be greater
07bac3
    than 1, not 0.
07bac3
07bac3
    (cherry picked from commit 0b4e2cd4c3192ba88569dd344f542a8cc43742b5)
07bac3
    (cherry picked from commit d01023aaac35543daffbdf48464e320150235d41)
07bac3
    (cherry picked from commit 59434b987e8eb436b08c24e559ee094c4e939daa)
07bac3
07bac3
commit 934effeb8a76f735ca1b07fbd00bc5c8e8d285a3
07bac3
Author: Evan Hunt <each@isc.org>
07bac3
Date:   Fri Apr 5 16:26:05 2019 -0700
07bac3
07bac3
    refactor tcpquota and pipeline refs; allow special-case overrun in isc_quota
07bac3
07bac3
    - if the TCP quota has been exceeded but there are no clients listening
07bac3
      for new connections on the interface, we can now force attachment to the
07bac3
      quota using isc_quota_force(), instead of carrying on with the quota not
07bac3
      attached.
07bac3
    - the TCP client quota is now referenced via a reference-counted
07bac3
      'ns_tcpconn' object, one of which is created whenever a client begins
07bac3
      listening for new connections, and attached to by members of that
07bac3
      client's pipeline group. when the last reference to the tcpconn
07bac3
      object is detached, it is freed and the TCP quota slot is released.
07bac3
    - reduce code duplication by adding mark_tcp_active() function.
07bac3
    - convert counters to atomic.
07bac3
07bac3
    (cherry picked from commit 7e8222378ca24f1302a0c1c638565050ab04681b)
07bac3
    (cherry picked from commit 4939451275722bfda490ea86ca13e84f6bc71e46)
07bac3
    (cherry picked from commit 13f7c918b8720d890408f678bd73c20e634539d9)
07bac3
    (cherry picked from commit c47ccf630f147378568b33e8fdb7b754f228c346)
07bac3
07bac3
    Remove usage of atomic operations, use classic locks
07bac3
07bac3
commit adb720fd828327ef7452fbe9248ab6a2e0c99d8f
07bac3
Author: Evan Hunt <each@isc.org>
07bac3
Date:   Fri Apr 5 16:12:18 2019 -0700
07bac3
07bac3
    better tcpquota accounting and client mortality checks
07bac3
07bac3
    - ensure that tcpactive is cleaned up correctly when accept() fails.
07bac3
    - set 'client->tcpattached' when the client is attached to the tcpquota.
07bac3
      carry this value on to new clients sharing the same pipeline group.
07bac3
      don't call isc_quota_detach() on the tcpquota unless tcpattached is
07bac3
      set.  this way clients that were allowed to accept TCP connections
07bac3
      despite being over quota (and therefore, were never attached to the
07bac3
      quota) will not inadvertently detach from it and mess up the
07bac3
      accounting.
07bac3
    - simplify the code for tcpquota disconnection by using a new function
07bac3
      tcpquota_disconnect().
07bac3
    - before deciding whether to reject a new connection due to quota
07bac3
      exhaustion, check to see whether there are at least two active
07bac3
      clients. previously, this was "at least one", but that could be
07bac3
      insufficient if there was one other client in READING state (waiting
07bac3
      for messages on an open connection) but none in READY (listening
07bac3
      for new connections).
07bac3
    - before deciding whether a TCP client object can to go inactive, we
07bac3
      must ensure there are enough other clients to maintain service
07bac3
      afterward -- both accepting new connections and reading/processing new
07bac3
      queries.  A TCP client can't shut down unless at least one
07bac3
      client is accepting new connections and (in the case of pipelined
07bac3
      clients) at least one additional client is waiting to read.
07bac3
07bac3
    (cherry picked from commit c7394738b2445c16f728a88394864dd61baad900)
07bac3
    (cherry picked from commit e965d5f11d3d0f6d59704e614fceca2093cb1856)
07bac3
    (cherry picked from commit 87d431161450777ea093821212abfb52d51b36e3)
07bac3
    (cherry picked from commit 2ab8a085b3c666f28f1f9229bd6ecb59915b26c3)
07bac3
07bac3
commit a2c8804aec66f3e44d7dbb6a1b0f64d6cd7110dd
07bac3
Author: Witold Kręcicki <wpk@isc.org>
07bac3
Date:   Fri Jan 4 12:50:51 2019 +0100
07bac3
07bac3
    tcp-clients could still be exceeded (v2)
07bac3
07bac3
    the TCP client quota could still be ineffective under some
07bac3
    circumstances.  this change:
07bac3
07bac3
    - improves quota accounting to ensure that TCP clients are
07bac3
      properly limited, while still guaranteeing that at least one client
07bac3
      is always available to serve TCP connections on each interface.
07bac3
    - uses more descriptive names and removes one (ntcptarget) that
07bac3
      was no longer needed
07bac3
    - adds comments
07bac3
07bac3
    (cherry picked from commit 924651f1d5e605cd186d03f4f7340bcc54d77cc2)
07bac3
    (cherry picked from commit 55a7a458e30e47874d34bdf1079eb863a0512396)
07bac3
    (cherry picked from commit 719f604e3fad5b7479bd14e2fa0ef4413f0a8fdc)
07bac3
07bac3
    Removed some unused parts
07bac3
07bac3
Patch: bind98-CVE-2018-5743.patch
07bac3
PatchNumber: 199
07bac3
---
07bac3
 bin/named/client.c                     | 273 ++++++++++++++++++++-----
07bac3
 bin/named/include/named/client.h       |   4 +-
07bac3
 bin/named/include/named/interfacemgr.h |  11 +-
07bac3
 bin/named/interfacemgr.c               |   8 +-
07bac3
 doc/arm/Bv9ARM-book.xml                |   3 +-
07bac3
 lib/isc/include/isc/quota.h            |   7 +
07bac3
 lib/isc/quota.c                        |  33 ++-
07bac3
 7 files changed, 275 insertions(+), 64 deletions(-)
07bac3
07bac3
diff --git a/bin/named/client.c b/bin/named/client.c
07bac3
index 9adf36b..c21b449 100644
07bac3
--- a/bin/named/client.c
07bac3
+++ b/bin/named/client.c
07bac3
@@ -279,6 +279,76 @@ ns_client_settimeout(ns_client_t *client, unsigned int seconds) {
07bac3
 }
07bac3
 
07bac3
 /*%
07bac3
+ * Allocate a reference-counted object that will maintain a single pointer to
07bac3
+ * the (also reference-counted) TCP client quota, shared between all the
07bac3
+ * clients processing queries on a single TCP connection.
07bac3
+ */
07bac3
+static isc_result_t
07bac3
+tcpconn_init(ns_client_t *client, isc_boolean_t force) {
07bac3
+	isc_result_t result;
07bac3
+	isc_quota_t *quota = NULL;
07bac3
+
07bac3
+	REQUIRE(client->tcpquota == NULL);
07bac3
+
07bac3
+	/*
07bac3
+	 * Try to attach to the quota first, so we won't pointlessly
07bac3
+	 * allocate memory for a tcpconn object if we can't get one.
07bac3
+	 */
07bac3
+	if (force) {
07bac3
+		result = isc_quota_force(&ns_g_server->tcpquota, "a;;
07bac3
+	} else {
07bac3
+		result = isc_quota_attach(&ns_g_server->tcpquota, "a;;
07bac3
+	}
07bac3
+	if (result != ISC_R_SUCCESS) {
07bac3
+		return (result);
07bac3
+	}
07bac3
+
07bac3
+	client->tcpquota = quota;
07bac3
+	quota = NULL;
07bac3
+
07bac3
+	return (ISC_R_SUCCESS);
07bac3
+}
07bac3
+
07bac3
+/*%
07bac3
+ * Decrease the count of client structures sharing the TCP connection that
07bac3
+ * 'client' is associated with.  If this is the last client using this TCP
07bac3
+ * connection, we detach from the TCP quota.
07bac3
+ */
07bac3
+static void
07bac3
+tcpconn_detach(ns_client_t *client) {
07bac3
+	REQUIRE(client->tcpquota != NULL);
07bac3
+
07bac3
+	isc_quota_detach(&client->tcpquota);
07bac3
+}
07bac3
+
07bac3
+/*%
07bac3
+ * Mark a client as active and increment the interface's 'ntcpactive'
07bac3
+ * counter, as a signal that there is at least one client servicing
07bac3
+ * TCP queries for the interface. If we reach the TCP client quota at
07bac3
+ * some point, this will be used to determine whether a quota overrun
07bac3
+ * should be permitted.
07bac3
+ *
07bac3
+ * Marking the client active with the 'tcpactive' flag ensures proper
07bac3
+ * accounting, by preventing us from incrementing or decrementing
07bac3
+ * 'ntcpactive' more than once per client.
07bac3
+ */
07bac3
+static void
07bac3
+mark_tcp_active(ns_client_t *client, isc_boolean_t active) {
07bac3
+	if (active && !client->tcpactive) {
07bac3
+		LOCK(&client->interface->lock);
07bac3
+		client->interface->ntcpactive++;
07bac3
+		client->tcpactive = active;
07bac3
+		UNLOCK(&client->interface->lock);
07bac3
+	} else if (!active && client->tcpactive) {
07bac3
+		LOCK(&client->interface->lock);
07bac3
+		INSIST(client->interface->ntcpactive > 0);
07bac3
+		client->interface->ntcpactive--;
07bac3
+		client->tcpactive = active;
07bac3
+		UNLOCK(&client->interface->lock);
07bac3
+	}
07bac3
+}
07bac3
+
07bac3
+/*
07bac3
  * Check for a deactivation or shutdown request and take appropriate
07bac3
  * action.  Returns ISC_TRUE if either is in progress; in this case
07bac3
  * the caller must no longer use the client object as it may have been
07bac3
@@ -367,6 +437,7 @@ exit_check(ns_client_t *client) {
07bac3
 		INSIST(client->recursionquota == NULL);
07bac3
 
07bac3
 		if (NS_CLIENTSTATE_READING == client->newstate) {
07bac3
+			INSIST(client->tcpquota != NULL);
07bac3
 			client_read(client);
07bac3
 			client->newstate = NS_CLIENTSTATE_MAX;
07bac3
 			return (ISC_TRUE); /* We're done. */
07bac3
@@ -380,10 +451,12 @@ exit_check(ns_client_t *client) {
07bac3
 		 */
07bac3
 		INSIST(client->recursionquota == NULL);
07bac3
 		INSIST(client->newstate <= NS_CLIENTSTATE_READY);
07bac3
+
07bac3
 		if (client->nreads > 0)
07bac3
 			dns_tcpmsg_cancelread(&client->tcpmsg);
07bac3
-		if (! client->nreads == 0) {
07bac3
-			/* Still waiting for read cancel completion. */
07bac3
+	
07bac3
+		/* Still waiting for read cancel completion. */
07bac3
+		if (client->nreads > 0) {
07bac3
 			return (ISC_TRUE);
07bac3
 		}
07bac3
 
07bac3
@@ -391,14 +464,50 @@ exit_check(ns_client_t *client) {
07bac3
 			dns_tcpmsg_invalidate(&client->tcpmsg);
07bac3
 			client->tcpmsg_valid = ISC_FALSE;
07bac3
 		}
07bac3
+
07bac3
+		/*
07bac3
+		 * Soon the client will be ready to accept a new TCP
07bac3
+		 * connection or UDP request, but we may have enough
07bac3
+		 * clients doing that already.  Check whether this client
07bac3
+		 * needs to remain active and allow it go inactive if
07bac3
+		 * not.
07bac3
+		 *
07bac3
+		 * UDP clients always go inactive at this point, but a TCP
07bac3
+		 * client may need to stay active and return to READY
07bac3
+		 * state if no other clients are available to listen
07bac3
+		 * for TCP requests on this interface.
07bac3
+		 *
07bac3
+		 * Regardless, if we're going to FREED state, that means
07bac3
+		 * the system is shutting down and we don't need to
07bac3
+		 * retain clients.
07bac3
+		 */
07bac3
+		LOCK(&client->interface->lock);
07bac3
+		if (client->mortal && TCP_CLIENT(client) &&
07bac3
+		    client->newstate != NS_CLIENTSTATE_FREED &&
07bac3
+		    !ns_g_clienttest &&
07bac3
+		    client->interface->ntcpaccepting == 0)
07bac3
+		{
07bac3
+			/* Nobody else is accepting */
07bac3
+			client->mortal = ISC_FALSE;
07bac3
+			client->newstate = NS_CLIENTSTATE_READY;
07bac3
+		}
07bac3
+		UNLOCK(&client->interface->lock);
07bac3
+
07bac3
+		/*
07bac3
+		 * Detach from TCP connection and TCP client quota,
07bac3
+		 * if appropriate. If this is the last reference to
07bac3
+		 * the TCP connection in our pipeline group, the
07bac3
+		 * TCP quota slot will be released.
07bac3
+		 */
07bac3
+		if (client->tcpquota) {
07bac3
+			tcpconn_detach(client);
07bac3
+		}
07bac3
 		if (client->tcpsocket != NULL) {
07bac3
 			CTRACE("closetcp");
07bac3
 			isc_socket_detach(&client->tcpsocket);
07bac3
+			mark_tcp_active(client, ISC_FALSE);
07bac3
 		}
07bac3
 
07bac3
-		if (client->tcpquota != NULL)
07bac3
-			isc_quota_detach(&client->tcpquota);
07bac3
-
07bac3
 		if (client->timerset) {
07bac3
 			(void)isc_timer_reset(client->timer,
07bac3
 					      isc_timertype_inactive,
07bac3
@@ -411,24 +520,6 @@ exit_check(ns_client_t *client) {
07bac3
 		client->state = NS_CLIENTSTATE_READY;
07bac3
 		INSIST(client->recursionquota == NULL);
07bac3
 
07bac3
-		/*
07bac3
-		 * Now the client is ready to accept a new TCP connection
07bac3
-		 * or UDP request, but we may have enough clients doing
07bac3
-		 * that already.  Check whether this client needs to remain
07bac3
-		 * active and force it to go inactive if not.
07bac3
-		 *
07bac3
-		 * UDP clients go inactive at this point, but TCP clients
07bac3
-		 * may remain active if we have fewer active TCP client
07bac3
-		 * objects than desired due to an earlier quota exhaustion.
07bac3
-		 */
07bac3
-		if (client->mortal && TCP_CLIENT(client) && !ns_g_clienttest) {
07bac3
-			LOCK(&client->interface->lock);
07bac3
-			if (client->interface->ntcpcurrent <
07bac3
-				    client->interface->ntcptarget)
07bac3
-				client->mortal = ISC_FALSE;
07bac3
-			UNLOCK(&client->interface->lock);
07bac3
-		}
07bac3
-
07bac3
 		/*
07bac3
 		 * We don't need the client; send it to the inactive
07bac3
 		 * queue for recycling.
07bac3
@@ -475,18 +566,20 @@ exit_check(ns_client_t *client) {
07bac3
 		if (client->nctls > 0)
07bac3
 			return (ISC_TRUE);
07bac3
 
07bac3
-		/* Deactivate the client. */
07bac3
-		if (client->interface)
07bac3
-			ns_interface_detach(&client->interface);
07bac3
-
07bac3
 		INSIST(client->naccepts == 0);
07bac3
 		INSIST(client->recursionquota == NULL);
07bac3
-		if (client->tcplistener != NULL)
07bac3
+		if (client->tcplistener != NULL) {
07bac3
 			isc_socket_detach(&client->tcplistener);
07bac3
+			mark_tcp_active(client, ISC_FALSE);
07bac3
+		}
07bac3
 
07bac3
 		if (client->udpsocket != NULL)
07bac3
 			isc_socket_detach(&client->udpsocket);
07bac3
 
07bac3
+		/* Deactivate the client. */
07bac3
+		if (client->interface)
07bac3
+			ns_interface_detach(&client->interface);
07bac3
+
07bac3
 		if (client->dispatch != NULL)
07bac3
 			dns_dispatch_detach(&client->dispatch);
07bac3
 
07bac3
@@ -605,13 +698,16 @@ client_start(isc_task_t *task, isc_event_t *event) {
07bac3
 		return;
07bac3
 
07bac3
 	if (TCP_CLIENT(client)) {
07bac3
-		client_accept(client);
07bac3
+		if (client->tcpquota != NULL) {
07bac3
+			client_read(client);
07bac3
+		} else {
07bac3
+			client_accept(client);
07bac3
+		}
07bac3
 	} else {
07bac3
 		client_udprecv(client);
07bac3
 	}
07bac3
 }
07bac3
 
07bac3
-
07bac3
 /*%
07bac3
  * The client's task has received a shutdown event.
07bac3
  */
07bac3
@@ -1499,6 +1595,7 @@ client_request(isc_task_t *task, isc_event_t *event) {
07bac3
 		client->nrecvs--;
07bac3
 	} else {
07bac3
 		INSIST(TCP_CLIENT(client));
07bac3
+		INSIST(client->tcpquota != NULL);
07bac3
 		REQUIRE(event->ev_type == DNS_EVENT_TCPMSG);
07bac3
 		REQUIRE(event->ev_sender == &client->tcpmsg);
07bac3
 		buffer = &client->tcpmsg.buffer;
07bac3
@@ -2146,6 +2243,7 @@ client_create(ns_clientmgr_t *manager, ns_client_t **clientp) {
07bac3
 	client->filter_aaaa = dns_v4_aaaa_ok;
07bac3
 #endif
07bac3
 	client->needshutdown = ns_g_clienttest;
07bac3
+	client->tcpactive = ISC_FALSE;
07bac3
 
07bac3
 	ISC_EVENT_INIT(&client->ctlevent, sizeof(client->ctlevent), 0, NULL,
07bac3
 		       NS_EVENT_CLIENTCONTROL, client_start, client, client,
07bac3
@@ -2240,22 +2338,29 @@ static void
07bac3
 client_newconn(isc_task_t *task, isc_event_t *event) {
07bac3
 	ns_client_t *client = event->ev_arg;
07bac3
 	isc_socket_newconnev_t *nevent = (isc_socket_newconnev_t *)event;
07bac3
-	isc_result_t result;
07bac3
 
07bac3
 	REQUIRE(event->ev_type == ISC_SOCKEVENT_NEWCONN);
07bac3
 	REQUIRE(NS_CLIENT_VALID(client));
07bac3
 	REQUIRE(client->task == task);
07bac3
+	REQUIRE(client->interface != NULL);
07bac3
 
07bac3
 	UNUSED(task);
07bac3
 
07bac3
 	INSIST(client->state == NS_CLIENTSTATE_READY);
07bac3
 
07bac3
+	/*
07bac3
+	 * The accept() was successful and we're now establishing a new
07bac3
+	 * connection. We need to make note of it in the client and
07bac3
+	 * interface objects so client objects can do the right thing
07bac3
+	 * when going inactive in exit_check() (see comments in
07bac3
+	 * client_accept() for details).
07bac3
+	 */
07bac3
 	INSIST(client->naccepts == 1);
07bac3
 	client->naccepts--;
07bac3
 
07bac3
 	LOCK(&client->interface->lock);
07bac3
-	INSIST(client->interface->ntcpcurrent > 0);
07bac3
-	client->interface->ntcpcurrent--;
07bac3
+	INSIST(client->interface->ntcpaccepting > 0);
07bac3
+	client->interface->ntcpaccepting--;
07bac3
 	UNLOCK(&client->interface->lock);
07bac3
 
07bac3
 	/*
07bac3
@@ -2289,6 +2394,7 @@ client_newconn(isc_task_t *task, isc_event_t *event) {
07bac3
 			      NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3),
07bac3
 			      "accept failed: %s",
07bac3
 			      isc_result_totext(nevent->result));
07bac3
+		tcpconn_detach(client);
07bac3
 	}
07bac3
 
07bac3
 	if (exit_check(client))
07bac3
@@ -2326,16 +2432,7 @@ client_newconn(isc_task_t *task, isc_event_t *event) {
07bac3
 		 * telnetting to port 53 (once per CPU) will
07bac3
 		 * deny service to legitimate TCP clients.
07bac3
 		 */
07bac3
-		result = isc_quota_attach(&ns_g_server->tcpquota,
07bac3
-					  &client->tcpquota);
07bac3
-		if (result == ISC_R_SUCCESS)
07bac3
-			result = ns_client_replace(client);
07bac3
-		if (result != ISC_R_SUCCESS) {
07bac3
-			ns_client_log(client, NS_LOGCATEGORY_CLIENT,
07bac3
-				      NS_LOGMODULE_CLIENT, ISC_LOG_WARNING,
07bac3
-				      "no more TCP clients: %s",
07bac3
-				      isc_result_totext(result));
07bac3
-		}
07bac3
+		(void)ns_client_replace(client);
07bac3
 
07bac3
 		client_read(client);
07bac3
 	}
07bac3
@@ -2350,12 +2447,67 @@ client_accept(ns_client_t *client) {
07bac3
 
07bac3
 	CTRACE("accept");
07bac3
 
07bac3
+	/*
07bac3
+	 * Set up a new TCP connection. This means try to attach to the
07bac3
+	 * TCP client quota (tcp-clients), but fail if we're over quota.
07bac3
+	 */
07bac3
+	result = tcpconn_init(client, ISC_FALSE);
07bac3
+	if (result != ISC_R_SUCCESS) {
07bac3
+		isc_boolean_t exit;
07bac3
+
07bac3
+		ns_client_log(client, NS_LOGCATEGORY_CLIENT,
07bac3
+			      NS_LOGMODULE_CLIENT, ISC_LOG_WARNING,
07bac3
+			      "TCP client quota reached: %s",
07bac3
+			      isc_result_totext(result));
07bac3
+
07bac3
+		/*
07bac3
+		 * We have exceeded the system-wide TCP client quota.  But,
07bac3
+		 * we can't just block this accept in all cases, because if
07bac3
+		 * we did, a heavy TCP load on other interfaces might cause
07bac3
+		 * this interface to be starved, with no clients able to
07bac3
+		 * accept new connections.
07bac3
+		 *
07bac3
+		 * So, we check here to see if any other clients are
07bac3
+		 * already servicing TCP queries on this interface (whether
07bac3
+		 * accepting, reading, or processing). If we find that at
07bac3
+		 * least one client other than this one is active, then
07bac3
+		 * it's okay *not* to call accept - we can let this
07bac3
+		 * client go inactive and another will take over when it's
07bac3
+		 * done.
07bac3
+		 *
07bac3
+		 * If there aren't enough active clients on the interface,
07bac3
+		 * then we can be a little bit flexible about the quota.
07bac3
+		 * We'll allow *one* extra client through to ensure we're
07bac3
+		 * listening on every interface; we do this by setting the
07bac3
+		 * 'force' option to tcpconn_init().
07bac3
+		 *
07bac3
+		 * (Note: In practice this means that the real TCP client
07bac3
+		 * quota is tcp-clients plus the number of listening
07bac3
+		 * interfaces plus 1.)
07bac3
+		 */
07bac3
+			LOCK(&client->interface->lock);
07bac3
+			exit = ISC_TF(client->interface->ntcpactive > (client->tcpactive ? 1 : 0));
07bac3
+			UNLOCK(&client->interface->lock);
07bac3
+		if (exit) {
07bac3
+			client->newstate = NS_CLIENTSTATE_INACTIVE;
07bac3
+			(void)exit_check(client);
07bac3
+			return;
07bac3
+		}
07bac3
+
07bac3
+		result = tcpconn_init(client, ISC_TRUE);
07bac3
+		RUNTIME_CHECK(result == ISC_R_SUCCESS);
07bac3
+	}
07bac3
+
07bac3
+	/*
07bac3
+	 * If this client was set up using get_client() or get_worker(),
07bac3
+	 * then TCP is already marked active. However, if it was restarted
07bac3
+	 * from exit_check(), it might not be, so we take care of it now.
07bac3
+	 */
07bac3
+	mark_tcp_active(client, ISC_TRUE);
07bac3
+
07bac3
 	result = isc_socket_accept(client->tcplistener, client->task,
07bac3
 				   client_newconn, client);
07bac3
 	if (result != ISC_R_SUCCESS) {
07bac3
-		UNEXPECTED_ERROR(__FILE__, __LINE__,
07bac3
-				 "isc_socket_accept() failed: %s",
07bac3
-				 isc_result_totext(result));
07bac3
 		/*
07bac3
 		 * XXXRTH  What should we do?  We're trying to accept but
07bac3
 		 *	   it didn't work.  If we just give up, then TCP
07bac3
@@ -2363,12 +2515,38 @@ client_accept(ns_client_t *client) {
07bac3
 		 *
07bac3
 		 *	   For now, we just go idle.
07bac3
 		 */
07bac3
+		UNEXPECTED_ERROR(__FILE__, __LINE__,
07bac3
+				 "isc_socket_accept() failed: %s",
07bac3
+				 isc_result_totext(result));
07bac3
+
07bac3
+		tcpconn_detach(client);
07bac3
+		mark_tcp_active(client, ISC_FALSE);
07bac3
 		return;
07bac3
 	}
07bac3
+
07bac3
+	/*
07bac3
+	 * The client's 'naccepts' counter indicates that this client has
07bac3
+	 * called accept() and is waiting for a new connection. It should
07bac3
+	 * never exceed 1.
07bac3
+	 */
07bac3
 	INSIST(client->naccepts == 0);
07bac3
 	client->naccepts++;
07bac3
+
07bac3
+	/*
07bac3
+	 * The interface's 'ntcpaccepting' counter is incremented when
07bac3
+	 * any client calls accept(), and decremented in client_newconn()
07bac3
+	 * once the connection is established.
07bac3
+	 *
07bac3
+	 * When the client object is shutting down after handling a TCP
07bac3
+	 * request (see exit_check()), if this value is at least one, that
07bac3
+	 * means another client has called accept() and is waiting to
07bac3
+	 * establish the next connection. That means the client may be
07bac3
+	 * be free to become inactive; otherwise it may need to start
07bac3
+	 * listening for connections itself to prevent the interface
07bac3
+	 * going dead.
07bac3
+	 */
07bac3
 	LOCK(&client->interface->lock);
07bac3
-	client->interface->ntcpcurrent++;
07bac3
+	client->interface->ntcpaccepting++;
07bac3
 	UNLOCK(&client->interface->lock);
07bac3
 }
07bac3
 
07bac3
@@ -2626,6 +2804,7 @@ get_client(ns_clientmgr_t *manager, ns_interface_t *ifp,
07bac3
 
07bac3
 	if (tcp) {
07bac3
 		client->attributes |= NS_CLIENTATTR_TCP;
07bac3
+		mark_tcp_active(client, ISC_TRUE);
07bac3
 		isc_socket_attach(ifp->tcpsocket,
07bac3
 				  &client->tcplistener);
07bac3
 	} else {
07bac3
diff --git a/bin/named/include/named/client.h b/bin/named/include/named/client.h
07bac3
index 98e79df..b210e61 100644
07bac3
--- a/bin/named/include/named/client.h
07bac3
+++ b/bin/named/include/named/client.h
07bac3
@@ -131,7 +131,7 @@ struct ns_client {
07bac3
 	isc_stdtime_t		requesttime;
07bac3
 	isc_stdtime_t		now;
07bac3
 	dns_name_t		signername;   /*%< [T]SIG key name */
07bac3
-	dns_name_t *		signer;	      /*%< NULL if not valid sig */
07bac3
+	dns_name_t		*signer;      /*%< NULL if not valid sig */
07bac3
 	isc_boolean_t		mortal;	      /*%< Die after handling request */
07bac3
 	isc_quota_t		*tcpquota;
07bac3
 	isc_quota_t		*recursionquota;
07bac3
@@ -159,6 +159,8 @@ struct ns_client {
07bac3
 	ISC_LINK(ns_client_t)	link;
07bac3
 	ISC_LINK(ns_client_t)	rlink;
07bac3
 	ISC_QLINK(ns_client_t)	ilink;
07bac3
+
07bac3
+	isc_boolean_t		tcpactive;
07bac3
 };
07bac3
 
07bac3
 typedef ISC_QUEUE(ns_client_t) client_queue_t;
07bac3
diff --git a/bin/named/include/named/interfacemgr.h b/bin/named/include/named/interfacemgr.h
07bac3
index 380dbed..56d953c 100644
07bac3
--- a/bin/named/include/named/interfacemgr.h
07bac3
+++ b/bin/named/include/named/interfacemgr.h
07bac3
@@ -80,9 +80,14 @@ struct ns_interface {
07bac3
 	dns_dispatch_t *	udpdispatch[MAX_UDP_DISPATCH];
07bac3
 						/*%< UDP dispatchers. */
07bac3
 	isc_socket_t *		tcpsocket;	/*%< TCP socket. */
07bac3
-	int			ntcptarget;	/*%< Desired number of concurrent
07bac3
-						     TCP accepts */
07bac3
-	int			ntcpcurrent;	/*%< Current ditto, locked */
07bac3
+	int32_t			ntcpaccepting;	/*%< Number of clients
07bac3
+						     ready to accept new
07bac3
+						     TCP connections on this
07bac3
+						     interface */
07bac3
+	int32_t			ntcpactive;	/*%< Number of clients
07bac3
+						     servicing TCP queries
07bac3
+						     (whether accepting or
07bac3
+						     connected) */
07bac3
 	int			nudpdispatch;	/*%< Number of UDP dispatches */
07bac3
 	ns_clientmgr_t *	clientmgr;	/*%< Client manager. */
07bac3
 	ISC_LINK(ns_interface_t) link;
07bac3
diff --git a/bin/named/interfacemgr.c b/bin/named/interfacemgr.c
07bac3
index 4aee47a..e3f8cee 100644
07bac3
--- a/bin/named/interfacemgr.c
07bac3
+++ b/bin/named/interfacemgr.c
07bac3
@@ -380,8 +380,8 @@ ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
07bac3
 	 * connections will be handled in parallel even though there is
07bac3
 	 * only one client initially.
07bac3
 	 */
07bac3
-	ifp->ntcptarget = 1;
07bac3
-	ifp->ntcpcurrent = 0;
07bac3
+	ifp->ntcpaccepting = 0;
07bac3
+	ifp->ntcpactive = 0;
07bac3
 	ifp->nudpdispatch = 0;
07bac3
 
07bac3
 	ISC_LINK_INIT(ifp, link);
07bac3
@@ -510,9 +510,7 @@ ns_interface_accepttcp(ns_interface_t *ifp) {
07bac3
 	 */
07bac3
 	(void)isc_socket_filter(ifp->tcpsocket, "dataready");
07bac3
 
07bac3
-	result = ns_clientmgr_createclients(ifp->clientmgr,
07bac3
-					    ifp->ntcptarget, ifp,
07bac3
-					    ISC_TRUE);
07bac3
+	result = ns_clientmgr_createclients(ifp->clientmgr, 1, ifp, ISC_TRUE);
07bac3
 	if (result != ISC_R_SUCCESS) {
07bac3
 		UNEXPECTED_ERROR(__FILE__, __LINE__,
07bac3
 				 "TCP ns_clientmgr_createclients(): %s",
07bac3
diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml
07bac3
index af194d9..aa567aa 100644
07bac3
--- a/doc/arm/Bv9ARM-book.xml
07bac3
+++ b/doc/arm/Bv9ARM-book.xml
07bac3
@@ -8067,7 +8067,8 @@ avoid-v6-udp-ports { 40000; range 50000 60000; };
07bac3
                 <para>
07bac3
 		  The number of file descriptors reserved for TCP, stdio,
07bac3
 		  etc.  This needs to be big enough to cover the number of
07bac3
-		  interfaces <command>named</command> listens on, <command>tcp-clients</command> as well as
07bac3
+		  interfaces <command>named</command> listens on plus
07bac3
+		  <command>tcp-clients</command>, as well as
07bac3
 		  to provide room for outgoing TCP queries and incoming zone
07bac3
 		  transfers.  The default is <literal>512</literal>.
07bac3
 		  The minimum value is <literal>128</literal> and the
07bac3
diff --git a/lib/isc/include/isc/quota.h b/lib/isc/include/isc/quota.h
07bac3
index 7b0d0d9..bb1a927 100644
07bac3
--- a/lib/isc/include/isc/quota.h
07bac3
+++ b/lib/isc/include/isc/quota.h
07bac3
@@ -107,6 +107,13 @@ isc_quota_attach(isc_quota_t *quota, isc_quota_t **p);
07bac3
  * quota if successful (ISC_R_SUCCESS or ISC_R_SOFTQUOTA).
07bac3
  */
07bac3
 
07bac3
+isc_result_t
07bac3
+isc_quota_force(isc_quota_t *quota, isc_quota_t **p);
07bac3
+/*%<
07bac3
+ * Like isc_quota_attach, but will attach '*p' to the quota
07bac3
+ * even if the hard quota has been exceeded.
07bac3
+ */
07bac3
+
07bac3
 void
07bac3
 isc_quota_detach(isc_quota_t **p);
07bac3
 /*%<
07bac3
diff --git a/lib/isc/quota.c b/lib/isc/quota.c
07bac3
index 5e5c50c..ca4c478 100644
07bac3
--- a/lib/isc/quota.c
07bac3
+++ b/lib/isc/quota.c
07bac3
@@ -81,20 +81,39 @@ isc_quota_release(isc_quota_t *quota) {
07bac3
 	UNLOCK(&quota->lock);
07bac3
 }
07bac3
 
07bac3
-isc_result_t
07bac3
-isc_quota_attach(isc_quota_t *quota, isc_quota_t **p)
07bac3
-{
07bac3
+static isc_result_t
07bac3
+doattach(isc_quota_t *quota, isc_quota_t **p, isc_boolean_t force) {
07bac3
 	isc_result_t result;
07bac3
-	INSIST(p != NULL && *p == NULL);
07bac3
+	REQUIRE(p != NULL && *p == NULL);
07bac3
+
07bac3
 	result = isc_quota_reserve(quota);
07bac3
-	if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA)
07bac3
+	if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA) {
07bac3
+		*p = quota;
07bac3
+	} else if (result == ISC_R_QUOTA && force) {
07bac3
+		/* attach anyway */
07bac3
+		LOCK(&quota->lock);
07bac3
+		quota->used++;
07bac3
+		UNLOCK(&quota->lock);
07bac3
+
07bac3
 		*p = quota;
07bac3
+		result = ISC_R_SUCCESS;
07bac3
+	}
07bac3
+
07bac3
 	return (result);
07bac3
 }
07bac3
 
07bac3
+isc_result_t
07bac3
+isc_quota_attach(isc_quota_t *quota, isc_quota_t **p) {
07bac3
+	return (doattach(quota, p, ISC_FALSE));
07bac3
+}
07bac3
+
07bac3
+isc_result_t
07bac3
+isc_quota_force(isc_quota_t *quota, isc_quota_t **p) {
07bac3
+	return (doattach(quota, p, ISC_TRUE));
07bac3
+}
07bac3
+
07bac3
 void
07bac3
-isc_quota_detach(isc_quota_t **p)
07bac3
-{
07bac3
+isc_quota_detach(isc_quota_t **p) {
07bac3
 	INSIST(p != NULL && *p != NULL);
07bac3
 	isc_quota_release(*p);
07bac3
 	*p = NULL;
07bac3
-- 
07bac3
2.20.1
07bac3