Blob Blame History Raw
From c8292710c84e40eeaea6a51df2842fc5f34458d5 Mon Sep 17 00:00:00 2001
From: Petr Mensik <pemensik@redhat.com>
Date: Wed, 5 Jun 2019 15:57:14 +0200
Subject: [PATCH] Fix CVE-2018-5743
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Squashed commit of the following:

commit f8e5335ea483c72341093f02d964b1ea9290077d
Author: Petr Mensik <pemensik@redhat.com>
Date:   Tue Jun 4 17:19:20 2019 +0200

    Remove pipelined parameter and tcpconn structure with reference counter

    It is not ever used in this version. Is not required to work.

    TCP connection is never shared in this version. Do not use reference
    counter, just attach and detach tcpquota to given client.

commit e3521d5531054b51e897e5458b09392cc752797d
Author: Evan Hunt <each@isc.org>
Date:   Fri Apr 5 16:26:19 2019 -0700

    restore allowance for tcp-clients < interfaces

    in the "refactor tcpquota and pipeline refs" commit, the counting
    of active interfaces was tightened in such a way that named could
    fail to listen on an interface if there were more interfaces than
    tcp-clients. when checking the quota to start accepting on an
    interface, if the number of active clients was above zero, then
    it was presumed that some other client was able to handle accepting
    new connections. this, however, ignored the fact that the current client
    could be included in that count, so if the quota was already exceeded
    before all the interfaces were listening, some interfaces would never
    listen.

    we now check whether the current client has been marked active; if so,
    then the number of active clients on the interface must be greater
    than 1, not 0.

    (cherry picked from commit 0b4e2cd4c3192ba88569dd344f542a8cc43742b5)
    (cherry picked from commit d01023aaac35543daffbdf48464e320150235d41)
    (cherry picked from commit 59434b987e8eb436b08c24e559ee094c4e939daa)

commit 934effeb8a76f735ca1b07fbd00bc5c8e8d285a3
Author: Evan Hunt <each@isc.org>
Date:   Fri Apr 5 16:26:05 2019 -0700

    refactor tcpquota and pipeline refs; allow special-case overrun in isc_quota

    - if the TCP quota has been exceeded but there are no clients listening
      for new connections on the interface, we can now force attachment to the
      quota using isc_quota_force(), instead of carrying on with the quota not
      attached.
    - the TCP client quota is now referenced via a reference-counted
      'ns_tcpconn' object, one of which is created whenever a client begins
      listening for new connections, and attached to by members of that
      client's pipeline group. when the last reference to the tcpconn
      object is detached, it is freed and the TCP quota slot is released.
    - reduce code duplication by adding mark_tcp_active() function.
    - convert counters to atomic.

    (cherry picked from commit 7e8222378ca24f1302a0c1c638565050ab04681b)
    (cherry picked from commit 4939451275722bfda490ea86ca13e84f6bc71e46)
    (cherry picked from commit 13f7c918b8720d890408f678bd73c20e634539d9)
    (cherry picked from commit c47ccf630f147378568b33e8fdb7b754f228c346)

    Remove usage of atomic operations, use classic locks

commit adb720fd828327ef7452fbe9248ab6a2e0c99d8f
Author: Evan Hunt <each@isc.org>
Date:   Fri Apr 5 16:12:18 2019 -0700

    better tcpquota accounting and client mortality checks

    - ensure that tcpactive is cleaned up correctly when accept() fails.
    - set 'client->tcpattached' when the client is attached to the tcpquota.
      carry this value on to new clients sharing the same pipeline group.
      don't call isc_quota_detach() on the tcpquota unless tcpattached is
      set.  this way clients that were allowed to accept TCP connections
      despite being over quota (and therefore, were never attached to the
      quota) will not inadvertently detach from it and mess up the
      accounting.
    - simplify the code for tcpquota disconnection by using a new function
      tcpquota_disconnect().
    - before deciding whether to reject a new connection due to quota
      exhaustion, check to see whether there are at least two active
      clients. previously, this was "at least one", but that could be
      insufficient if there was one other client in READING state (waiting
      for messages on an open connection) but none in READY (listening
      for new connections).
    - before deciding whether a TCP client object can to go inactive, we
      must ensure there are enough other clients to maintain service
      afterward -- both accepting new connections and reading/processing new
      queries.  A TCP client can't shut down unless at least one
      client is accepting new connections and (in the case of pipelined
      clients) at least one additional client is waiting to read.

    (cherry picked from commit c7394738b2445c16f728a88394864dd61baad900)
    (cherry picked from commit e965d5f11d3d0f6d59704e614fceca2093cb1856)
    (cherry picked from commit 87d431161450777ea093821212abfb52d51b36e3)
    (cherry picked from commit 2ab8a085b3c666f28f1f9229bd6ecb59915b26c3)

commit a2c8804aec66f3e44d7dbb6a1b0f64d6cd7110dd
Author: Witold Kręcicki <wpk@isc.org>
Date:   Fri Jan 4 12:50:51 2019 +0100

    tcp-clients could still be exceeded (v2)

    the TCP client quota could still be ineffective under some
    circumstances.  this change:

    - improves quota accounting to ensure that TCP clients are
      properly limited, while still guaranteeing that at least one client
      is always available to serve TCP connections on each interface.
    - uses more descriptive names and removes one (ntcptarget) that
      was no longer needed
    - adds comments

    (cherry picked from commit 924651f1d5e605cd186d03f4f7340bcc54d77cc2)
    (cherry picked from commit 55a7a458e30e47874d34bdf1079eb863a0512396)
    (cherry picked from commit 719f604e3fad5b7479bd14e2fa0ef4413f0a8fdc)

    Removed some unused parts

Patch: bind98-CVE-2018-5743.patch
PatchNumber: 199
---
 bin/named/client.c                     | 273 ++++++++++++++++++++-----
 bin/named/include/named/client.h       |   4 +-
 bin/named/include/named/interfacemgr.h |  11 +-
 bin/named/interfacemgr.c               |   8 +-
 doc/arm/Bv9ARM-book.xml                |   3 +-
 lib/isc/include/isc/quota.h            |   7 +
 lib/isc/quota.c                        |  33 ++-
 7 files changed, 275 insertions(+), 64 deletions(-)

diff --git a/bin/named/client.c b/bin/named/client.c
index 9adf36b..c21b449 100644
--- a/bin/named/client.c
+++ b/bin/named/client.c
@@ -279,6 +279,76 @@ ns_client_settimeout(ns_client_t *client, unsigned int seconds) {
 }
 
 /*%
+ * Allocate a reference-counted object that will maintain a single pointer to
+ * the (also reference-counted) TCP client quota, shared between all the
+ * clients processing queries on a single TCP connection.
+ */
+static isc_result_t
+tcpconn_init(ns_client_t *client, isc_boolean_t force) {
+	isc_result_t result;
+	isc_quota_t *quota = NULL;
+
+	REQUIRE(client->tcpquota == NULL);
+
+	/*
+	 * Try to attach to the quota first, so we won't pointlessly
+	 * allocate memory for a tcpconn object if we can't get one.
+	 */
+	if (force) {
+		result = isc_quota_force(&ns_g_server->tcpquota, &quota);
+	} else {
+		result = isc_quota_attach(&ns_g_server->tcpquota, &quota);
+	}
+	if (result != ISC_R_SUCCESS) {
+		return (result);
+	}
+
+	client->tcpquota = quota;
+	quota = NULL;
+
+	return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Decrease the count of client structures sharing the TCP connection that
+ * 'client' is associated with.  If this is the last client using this TCP
+ * connection, we detach from the TCP quota.
+ */
+static void
+tcpconn_detach(ns_client_t *client) {
+	REQUIRE(client->tcpquota != NULL);
+
+	isc_quota_detach(&client->tcpquota);
+}
+
+/*%
+ * Mark a client as active and increment the interface's 'ntcpactive'
+ * counter, as a signal that there is at least one client servicing
+ * TCP queries for the interface. If we reach the TCP client quota at
+ * some point, this will be used to determine whether a quota overrun
+ * should be permitted.
+ *
+ * Marking the client active with the 'tcpactive' flag ensures proper
+ * accounting, by preventing us from incrementing or decrementing
+ * 'ntcpactive' more than once per client.
+ */
+static void
+mark_tcp_active(ns_client_t *client, isc_boolean_t active) {
+	if (active && !client->tcpactive) {
+		LOCK(&client->interface->lock);
+		client->interface->ntcpactive++;
+		client->tcpactive = active;
+		UNLOCK(&client->interface->lock);
+	} else if (!active && client->tcpactive) {
+		LOCK(&client->interface->lock);
+		INSIST(client->interface->ntcpactive > 0);
+		client->interface->ntcpactive--;
+		client->tcpactive = active;
+		UNLOCK(&client->interface->lock);
+	}
+}
+
+/*
  * Check for a deactivation or shutdown request and take appropriate
  * action.  Returns ISC_TRUE if either is in progress; in this case
  * the caller must no longer use the client object as it may have been
@@ -367,6 +437,7 @@ exit_check(ns_client_t *client) {
 		INSIST(client->recursionquota == NULL);
 
 		if (NS_CLIENTSTATE_READING == client->newstate) {
+			INSIST(client->tcpquota != NULL);
 			client_read(client);
 			client->newstate = NS_CLIENTSTATE_MAX;
 			return (ISC_TRUE); /* We're done. */
@@ -380,10 +451,12 @@ exit_check(ns_client_t *client) {
 		 */
 		INSIST(client->recursionquota == NULL);
 		INSIST(client->newstate <= NS_CLIENTSTATE_READY);
+
 		if (client->nreads > 0)
 			dns_tcpmsg_cancelread(&client->tcpmsg);
-		if (! client->nreads == 0) {
-			/* Still waiting for read cancel completion. */
+	
+		/* Still waiting for read cancel completion. */
+		if (client->nreads > 0) {
 			return (ISC_TRUE);
 		}
 
@@ -391,14 +464,50 @@ exit_check(ns_client_t *client) {
 			dns_tcpmsg_invalidate(&client->tcpmsg);
 			client->tcpmsg_valid = ISC_FALSE;
 		}
+
+		/*
+		 * Soon the client will be ready to accept a new TCP
+		 * connection or UDP request, but we may have enough
+		 * clients doing that already.  Check whether this client
+		 * needs to remain active and allow it go inactive if
+		 * not.
+		 *
+		 * UDP clients always go inactive at this point, but a TCP
+		 * client may need to stay active and return to READY
+		 * state if no other clients are available to listen
+		 * for TCP requests on this interface.
+		 *
+		 * Regardless, if we're going to FREED state, that means
+		 * the system is shutting down and we don't need to
+		 * retain clients.
+		 */
+		LOCK(&client->interface->lock);
+		if (client->mortal && TCP_CLIENT(client) &&
+		    client->newstate != NS_CLIENTSTATE_FREED &&
+		    !ns_g_clienttest &&
+		    client->interface->ntcpaccepting == 0)
+		{
+			/* Nobody else is accepting */
+			client->mortal = ISC_FALSE;
+			client->newstate = NS_CLIENTSTATE_READY;
+		}
+		UNLOCK(&client->interface->lock);
+
+		/*
+		 * Detach from TCP connection and TCP client quota,
+		 * if appropriate. If this is the last reference to
+		 * the TCP connection in our pipeline group, the
+		 * TCP quota slot will be released.
+		 */
+		if (client->tcpquota) {
+			tcpconn_detach(client);
+		}
 		if (client->tcpsocket != NULL) {
 			CTRACE("closetcp");
 			isc_socket_detach(&client->tcpsocket);
+			mark_tcp_active(client, ISC_FALSE);
 		}
 
-		if (client->tcpquota != NULL)
-			isc_quota_detach(&client->tcpquota);
-
 		if (client->timerset) {
 			(void)isc_timer_reset(client->timer,
 					      isc_timertype_inactive,
@@ -411,24 +520,6 @@ exit_check(ns_client_t *client) {
 		client->state = NS_CLIENTSTATE_READY;
 		INSIST(client->recursionquota == NULL);
 
-		/*
-		 * Now the client is ready to accept a new TCP connection
-		 * or UDP request, but we may have enough clients doing
-		 * that already.  Check whether this client needs to remain
-		 * active and force it to go inactive if not.
-		 *
-		 * UDP clients go inactive at this point, but TCP clients
-		 * may remain active if we have fewer active TCP client
-		 * objects than desired due to an earlier quota exhaustion.
-		 */
-		if (client->mortal && TCP_CLIENT(client) && !ns_g_clienttest) {
-			LOCK(&client->interface->lock);
-			if (client->interface->ntcpcurrent <
-				    client->interface->ntcptarget)
-				client->mortal = ISC_FALSE;
-			UNLOCK(&client->interface->lock);
-		}
-
 		/*
 		 * We don't need the client; send it to the inactive
 		 * queue for recycling.
@@ -475,18 +566,20 @@ exit_check(ns_client_t *client) {
 		if (client->nctls > 0)
 			return (ISC_TRUE);
 
-		/* Deactivate the client. */
-		if (client->interface)
-			ns_interface_detach(&client->interface);
-
 		INSIST(client->naccepts == 0);
 		INSIST(client->recursionquota == NULL);
-		if (client->tcplistener != NULL)
+		if (client->tcplistener != NULL) {
 			isc_socket_detach(&client->tcplistener);
+			mark_tcp_active(client, ISC_FALSE);
+		}
 
 		if (client->udpsocket != NULL)
 			isc_socket_detach(&client->udpsocket);
 
+		/* Deactivate the client. */
+		if (client->interface)
+			ns_interface_detach(&client->interface);
+
 		if (client->dispatch != NULL)
 			dns_dispatch_detach(&client->dispatch);
 
@@ -605,13 +698,16 @@ client_start(isc_task_t *task, isc_event_t *event) {
 		return;
 
 	if (TCP_CLIENT(client)) {
-		client_accept(client);
+		if (client->tcpquota != NULL) {
+			client_read(client);
+		} else {
+			client_accept(client);
+		}
 	} else {
 		client_udprecv(client);
 	}
 }
 
-
 /*%
  * The client's task has received a shutdown event.
  */
@@ -1499,6 +1595,7 @@ client_request(isc_task_t *task, isc_event_t *event) {
 		client->nrecvs--;
 	} else {
 		INSIST(TCP_CLIENT(client));
+		INSIST(client->tcpquota != NULL);
 		REQUIRE(event->ev_type == DNS_EVENT_TCPMSG);
 		REQUIRE(event->ev_sender == &client->tcpmsg);
 		buffer = &client->tcpmsg.buffer;
@@ -2146,6 +2243,7 @@ client_create(ns_clientmgr_t *manager, ns_client_t **clientp) {
 	client->filter_aaaa = dns_v4_aaaa_ok;
 #endif
 	client->needshutdown = ns_g_clienttest;
+	client->tcpactive = ISC_FALSE;
 
 	ISC_EVENT_INIT(&client->ctlevent, sizeof(client->ctlevent), 0, NULL,
 		       NS_EVENT_CLIENTCONTROL, client_start, client, client,
@@ -2240,22 +2338,29 @@ static void
 client_newconn(isc_task_t *task, isc_event_t *event) {
 	ns_client_t *client = event->ev_arg;
 	isc_socket_newconnev_t *nevent = (isc_socket_newconnev_t *)event;
-	isc_result_t result;
 
 	REQUIRE(event->ev_type == ISC_SOCKEVENT_NEWCONN);
 	REQUIRE(NS_CLIENT_VALID(client));
 	REQUIRE(client->task == task);
+	REQUIRE(client->interface != NULL);
 
 	UNUSED(task);
 
 	INSIST(client->state == NS_CLIENTSTATE_READY);
 
+	/*
+	 * The accept() was successful and we're now establishing a new
+	 * connection. We need to make note of it in the client and
+	 * interface objects so client objects can do the right thing
+	 * when going inactive in exit_check() (see comments in
+	 * client_accept() for details).
+	 */
 	INSIST(client->naccepts == 1);
 	client->naccepts--;
 
 	LOCK(&client->interface->lock);
-	INSIST(client->interface->ntcpcurrent > 0);
-	client->interface->ntcpcurrent--;
+	INSIST(client->interface->ntcpaccepting > 0);
+	client->interface->ntcpaccepting--;
 	UNLOCK(&client->interface->lock);
 
 	/*
@@ -2289,6 +2394,7 @@ client_newconn(isc_task_t *task, isc_event_t *event) {
 			      NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3),
 			      "accept failed: %s",
 			      isc_result_totext(nevent->result));
+		tcpconn_detach(client);
 	}
 
 	if (exit_check(client))
@@ -2326,16 +2432,7 @@ client_newconn(isc_task_t *task, isc_event_t *event) {
 		 * telnetting to port 53 (once per CPU) will
 		 * deny service to legitimate TCP clients.
 		 */
-		result = isc_quota_attach(&ns_g_server->tcpquota,
-					  &client->tcpquota);
-		if (result == ISC_R_SUCCESS)
-			result = ns_client_replace(client);
-		if (result != ISC_R_SUCCESS) {
-			ns_client_log(client, NS_LOGCATEGORY_CLIENT,
-				      NS_LOGMODULE_CLIENT, ISC_LOG_WARNING,
-				      "no more TCP clients: %s",
-				      isc_result_totext(result));
-		}
+		(void)ns_client_replace(client);
 
 		client_read(client);
 	}
@@ -2350,12 +2447,67 @@ client_accept(ns_client_t *client) {
 
 	CTRACE("accept");
 
+	/*
+	 * Set up a new TCP connection. This means try to attach to the
+	 * TCP client quota (tcp-clients), but fail if we're over quota.
+	 */
+	result = tcpconn_init(client, ISC_FALSE);
+	if (result != ISC_R_SUCCESS) {
+		isc_boolean_t exit;
+
+		ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+			      NS_LOGMODULE_CLIENT, ISC_LOG_WARNING,
+			      "TCP client quota reached: %s",
+			      isc_result_totext(result));
+
+		/*
+		 * We have exceeded the system-wide TCP client quota.  But,
+		 * we can't just block this accept in all cases, because if
+		 * we did, a heavy TCP load on other interfaces might cause
+		 * this interface to be starved, with no clients able to
+		 * accept new connections.
+		 *
+		 * So, we check here to see if any other clients are
+		 * already servicing TCP queries on this interface (whether
+		 * accepting, reading, or processing). If we find that at
+		 * least one client other than this one is active, then
+		 * it's okay *not* to call accept - we can let this
+		 * client go inactive and another will take over when it's
+		 * done.
+		 *
+		 * If there aren't enough active clients on the interface,
+		 * then we can be a little bit flexible about the quota.
+		 * We'll allow *one* extra client through to ensure we're
+		 * listening on every interface; we do this by setting the
+		 * 'force' option to tcpconn_init().
+		 *
+		 * (Note: In practice this means that the real TCP client
+		 * quota is tcp-clients plus the number of listening
+		 * interfaces plus 1.)
+		 */
+			LOCK(&client->interface->lock);
+			exit = ISC_TF(client->interface->ntcpactive > (client->tcpactive ? 1 : 0));
+			UNLOCK(&client->interface->lock);
+		if (exit) {
+			client->newstate = NS_CLIENTSTATE_INACTIVE;
+			(void)exit_check(client);
+			return;
+		}
+
+		result = tcpconn_init(client, ISC_TRUE);
+		RUNTIME_CHECK(result == ISC_R_SUCCESS);
+	}
+
+	/*
+	 * If this client was set up using get_client() or get_worker(),
+	 * then TCP is already marked active. However, if it was restarted
+	 * from exit_check(), it might not be, so we take care of it now.
+	 */
+	mark_tcp_active(client, ISC_TRUE);
+
 	result = isc_socket_accept(client->tcplistener, client->task,
 				   client_newconn, client);
 	if (result != ISC_R_SUCCESS) {
-		UNEXPECTED_ERROR(__FILE__, __LINE__,
-				 "isc_socket_accept() failed: %s",
-				 isc_result_totext(result));
 		/*
 		 * XXXRTH  What should we do?  We're trying to accept but
 		 *	   it didn't work.  If we just give up, then TCP
@@ -2363,12 +2515,38 @@ client_accept(ns_client_t *client) {
 		 *
 		 *	   For now, we just go idle.
 		 */
+		UNEXPECTED_ERROR(__FILE__, __LINE__,
+				 "isc_socket_accept() failed: %s",
+				 isc_result_totext(result));
+
+		tcpconn_detach(client);
+		mark_tcp_active(client, ISC_FALSE);
 		return;
 	}
+
+	/*
+	 * The client's 'naccepts' counter indicates that this client has
+	 * called accept() and is waiting for a new connection. It should
+	 * never exceed 1.
+	 */
 	INSIST(client->naccepts == 0);
 	client->naccepts++;
+
+	/*
+	 * The interface's 'ntcpaccepting' counter is incremented when
+	 * any client calls accept(), and decremented in client_newconn()
+	 * once the connection is established.
+	 *
+	 * When the client object is shutting down after handling a TCP
+	 * request (see exit_check()), if this value is at least one, that
+	 * means another client has called accept() and is waiting to
+	 * establish the next connection. That means the client may be
+	 * be free to become inactive; otherwise it may need to start
+	 * listening for connections itself to prevent the interface
+	 * going dead.
+	 */
 	LOCK(&client->interface->lock);
-	client->interface->ntcpcurrent++;
+	client->interface->ntcpaccepting++;
 	UNLOCK(&client->interface->lock);
 }
 
@@ -2626,6 +2804,7 @@ get_client(ns_clientmgr_t *manager, ns_interface_t *ifp,
 
 	if (tcp) {
 		client->attributes |= NS_CLIENTATTR_TCP;
+		mark_tcp_active(client, ISC_TRUE);
 		isc_socket_attach(ifp->tcpsocket,
 				  &client->tcplistener);
 	} else {
diff --git a/bin/named/include/named/client.h b/bin/named/include/named/client.h
index 98e79df..b210e61 100644
--- a/bin/named/include/named/client.h
+++ b/bin/named/include/named/client.h
@@ -131,7 +131,7 @@ struct ns_client {
 	isc_stdtime_t		requesttime;
 	isc_stdtime_t		now;
 	dns_name_t		signername;   /*%< [T]SIG key name */
-	dns_name_t *		signer;	      /*%< NULL if not valid sig */
+	dns_name_t		*signer;      /*%< NULL if not valid sig */
 	isc_boolean_t		mortal;	      /*%< Die after handling request */
 	isc_quota_t		*tcpquota;
 	isc_quota_t		*recursionquota;
@@ -159,6 +159,8 @@ struct ns_client {
 	ISC_LINK(ns_client_t)	link;
 	ISC_LINK(ns_client_t)	rlink;
 	ISC_QLINK(ns_client_t)	ilink;
+
+	isc_boolean_t		tcpactive;
 };
 
 typedef ISC_QUEUE(ns_client_t) client_queue_t;
diff --git a/bin/named/include/named/interfacemgr.h b/bin/named/include/named/interfacemgr.h
index 380dbed..56d953c 100644
--- a/bin/named/include/named/interfacemgr.h
+++ b/bin/named/include/named/interfacemgr.h
@@ -80,9 +80,14 @@ struct ns_interface {
 	dns_dispatch_t *	udpdispatch[MAX_UDP_DISPATCH];
 						/*%< UDP dispatchers. */
 	isc_socket_t *		tcpsocket;	/*%< TCP socket. */
-	int			ntcptarget;	/*%< Desired number of concurrent
-						     TCP accepts */
-	int			ntcpcurrent;	/*%< Current ditto, locked */
+	int32_t			ntcpaccepting;	/*%< Number of clients
+						     ready to accept new
+						     TCP connections on this
+						     interface */
+	int32_t			ntcpactive;	/*%< Number of clients
+						     servicing TCP queries
+						     (whether accepting or
+						     connected) */
 	int			nudpdispatch;	/*%< Number of UDP dispatches */
 	ns_clientmgr_t *	clientmgr;	/*%< Client manager. */
 	ISC_LINK(ns_interface_t) link;
diff --git a/bin/named/interfacemgr.c b/bin/named/interfacemgr.c
index 4aee47a..e3f8cee 100644
--- a/bin/named/interfacemgr.c
+++ b/bin/named/interfacemgr.c
@@ -380,8 +380,8 @@ ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
 	 * connections will be handled in parallel even though there is
 	 * only one client initially.
 	 */
-	ifp->ntcptarget = 1;
-	ifp->ntcpcurrent = 0;
+	ifp->ntcpaccepting = 0;
+	ifp->ntcpactive = 0;
 	ifp->nudpdispatch = 0;
 
 	ISC_LINK_INIT(ifp, link);
@@ -510,9 +510,7 @@ ns_interface_accepttcp(ns_interface_t *ifp) {
 	 */
 	(void)isc_socket_filter(ifp->tcpsocket, "dataready");
 
-	result = ns_clientmgr_createclients(ifp->clientmgr,
-					    ifp->ntcptarget, ifp,
-					    ISC_TRUE);
+	result = ns_clientmgr_createclients(ifp->clientmgr, 1, ifp, ISC_TRUE);
 	if (result != ISC_R_SUCCESS) {
 		UNEXPECTED_ERROR(__FILE__, __LINE__,
 				 "TCP ns_clientmgr_createclients(): %s",
diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml
index af194d9..aa567aa 100644
--- a/doc/arm/Bv9ARM-book.xml
+++ b/doc/arm/Bv9ARM-book.xml
@@ -8067,7 +8067,8 @@ avoid-v6-udp-ports { 40000; range 50000 60000; };
                 <para>
 		  The number of file descriptors reserved for TCP, stdio,
 		  etc.  This needs to be big enough to cover the number of
-		  interfaces <command>named</command> listens on, <command>tcp-clients</command> as well as
+		  interfaces <command>named</command> listens on plus
+		  <command>tcp-clients</command>, as well as
 		  to provide room for outgoing TCP queries and incoming zone
 		  transfers.  The default is <literal>512</literal>.
 		  The minimum value is <literal>128</literal> and the
diff --git a/lib/isc/include/isc/quota.h b/lib/isc/include/isc/quota.h
index 7b0d0d9..bb1a927 100644
--- a/lib/isc/include/isc/quota.h
+++ b/lib/isc/include/isc/quota.h
@@ -107,6 +107,13 @@ isc_quota_attach(isc_quota_t *quota, isc_quota_t **p);
  * quota if successful (ISC_R_SUCCESS or ISC_R_SOFTQUOTA).
  */
 
+isc_result_t
+isc_quota_force(isc_quota_t *quota, isc_quota_t **p);
+/*%<
+ * Like isc_quota_attach, but will attach '*p' to the quota
+ * even if the hard quota has been exceeded.
+ */
+
 void
 isc_quota_detach(isc_quota_t **p);
 /*%<
diff --git a/lib/isc/quota.c b/lib/isc/quota.c
index 5e5c50c..ca4c478 100644
--- a/lib/isc/quota.c
+++ b/lib/isc/quota.c
@@ -81,20 +81,39 @@ isc_quota_release(isc_quota_t *quota) {
 	UNLOCK(&quota->lock);
 }
 
-isc_result_t
-isc_quota_attach(isc_quota_t *quota, isc_quota_t **p)
-{
+static isc_result_t
+doattach(isc_quota_t *quota, isc_quota_t **p, isc_boolean_t force) {
 	isc_result_t result;
-	INSIST(p != NULL && *p == NULL);
+	REQUIRE(p != NULL && *p == NULL);
+
 	result = isc_quota_reserve(quota);
-	if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA)
+	if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA) {
+		*p = quota;
+	} else if (result == ISC_R_QUOTA && force) {
+		/* attach anyway */
+		LOCK(&quota->lock);
+		quota->used++;
+		UNLOCK(&quota->lock);
+
 		*p = quota;
+		result = ISC_R_SUCCESS;
+	}
+
 	return (result);
 }
 
+isc_result_t
+isc_quota_attach(isc_quota_t *quota, isc_quota_t **p) {
+	return (doattach(quota, p, ISC_FALSE));
+}
+
+isc_result_t
+isc_quota_force(isc_quota_t *quota, isc_quota_t **p) {
+	return (doattach(quota, p, ISC_TRUE));
+}
+
 void
-isc_quota_detach(isc_quota_t **p)
-{
+isc_quota_detach(isc_quota_t **p) {
 	INSIST(p != NULL && *p != NULL);
 	isc_quota_release(*p);
 	*p = NULL;
-- 
2.20.1