e07df0
From c3314d0fa0756d39cab1e9d9e3cf2e36dd6273da Mon Sep 17 00:00:00 2001
e07df0
From: Petr Mensik <pemensik@redhat.com>
e07df0
Date: Mon, 18 Nov 2019 16:44:58 +0100
e07df0
Subject: [PATCH] Limit number of queries per TCP connection
e07df0
e07df0
5306.	[security]	Set a limit on the number of concurrently served
e07df0
			pipelined TCP queries. (CVE-2019-6477) [GL #1264]
e07df0
---
e07df0
 bin/named/client.c               | 73 ++++++++++++++++++++------------
e07df0
 bin/named/include/named/client.h |  5 ++-
e07df0
 2 files changed, 50 insertions(+), 28 deletions(-)
e07df0
e07df0
diff --git a/bin/named/client.c b/bin/named/client.c
e07df0
index f21a77ba52..23f70edaff 100644
e07df0
--- a/bin/named/client.c
e07df0
+++ b/bin/named/client.c
e07df0
@@ -98,7 +98,15 @@
e07df0
 #define SEND_BUFFER_SIZE		4096
e07df0
 #define RECV_BUFFER_SIZE		4096
e07df0
 
e07df0
+#define TCP_CLIENTS_PER_CONN		23
e07df0
+/*%<
e07df0
+ * Number of simultaneous ns_clients_t (queries in flight) for one
e07df0
+ * TCP connection.  The number was arbitrarily picked and might be
e07df0
+ * changed in the future.
e07df0
+ */
e07df0
+
e07df0
 #ifdef ISC_PLATFORM_USETHREADS
e07df0
+
e07df0
 #define NMCTXS				100
e07df0
 /*%<
e07df0
  * Number of 'mctx pools' for clients. (Should this be configurable?)
e07df0
@@ -333,7 +341,7 @@ tcpconn_init(ns_client_t *client, isc_boolean_t force) {
e07df0
 	 */
e07df0
 	tconn = isc_mem_allocate(ns_g_mctx, sizeof(*tconn));
e07df0
 
e07df0
-	isc_refcount_init(&tconn->refs, 1);
e07df0
+	isc_refcount_init(&tconn->clients, 1);	/* Current client */
e07df0
 	tconn->tcpquota = quota;
e07df0
 	quota = NULL;
e07df0
 	tconn->pipelined = ISC_FALSE;
e07df0
@@ -350,14 +358,14 @@ tcpconn_init(ns_client_t *client, isc_boolean_t force) {
e07df0
  */
e07df0
 static void
e07df0
 tcpconn_attach(ns_client_t *source, ns_client_t *target) {
e07df0
-	int refs;
e07df0
+	int old_clients;
e07df0
 
e07df0
 	REQUIRE(source->tcpconn != NULL);
e07df0
 	REQUIRE(target->tcpconn == NULL);
e07df0
 	REQUIRE(source->tcpconn->pipelined);
e07df0
 
e07df0
-	isc_refcount_increment(&source->tcpconn->refs, &refs);
e07df0
-	INSIST(refs > 1);
e07df0
+	isc_refcount_increment(&source->tcpconn->clients, &old_clients);
e07df0
+	INSIST(old_clients > 1);
e07df0
 	target->tcpconn = source->tcpconn;
e07df0
 }
e07df0
 
e07df0
@@ -370,15 +378,15 @@ tcpconn_attach(ns_client_t *source, ns_client_t *target) {
e07df0
 static void
e07df0
 tcpconn_detach(ns_client_t *client) {
e07df0
 	ns_tcpconn_t *tconn = NULL;
e07df0
-	int refs;
e07df0
+	int old_clients;
e07df0
 
e07df0
 	REQUIRE(client->tcpconn != NULL);
e07df0
 
e07df0
 	tconn = client->tcpconn;
e07df0
 	client->tcpconn = NULL;
e07df0
 
e07df0
-	isc_refcount_decrement(&tconn->refs, &refs);
e07df0
-	if (refs == 0) {
e07df0
+	isc_refcount_decrement(&tconn->clients, &old_clients);
e07df0
+	if (old_clients == 0) {
e07df0
 		isc_quota_detach(&tconn->tcpquota);
e07df0
 		isc_mem_free(ns_g_mctx, tconn);
e07df0
 	}
e07df0
@@ -2629,28 +2637,39 @@ client_request(isc_task_t *task, isc_event_t *event) {
e07df0
 	/*
e07df0
 	 * Pipeline TCP query processing.
e07df0
 	 */
e07df0
-	if (TCP_CLIENT(client) &&
e07df0
-	    client->message->opcode != dns_opcode_query)
e07df0
-	{
e07df0
-		client->tcpconn->pipelined = ISC_FALSE;
e07df0
-	}
e07df0
-	if (TCP_CLIENT(client) && client->tcpconn->pipelined) {
e07df0
-		/*
e07df0
-		 * We're pipelining. Replace the client; the
e07df0
-		 * replacement can read the TCP socket looking
e07df0
-		 * for new messages and this one can process the
e07df0
-		 * current message asynchronously.
e07df0
-		 *
e07df0
-		 * There will now be at least three clients using this
e07df0
-		 * TCP socket - one accepting new connections,
e07df0
-		 * one reading an existing connection to get new
e07df0
-		 * messages, and one answering the message already
e07df0
-		 * received.
e07df0
-		 */
e07df0
-		result = ns_client_replace(client);
e07df0
-		if (result != ISC_R_SUCCESS) {
e07df0
+	if (TCP_CLIENT(client)) {
e07df0
+		if (client->message->opcode != dns_opcode_query) {
e07df0
 			client->tcpconn->pipelined = ISC_FALSE;
e07df0
 		}
e07df0
+
e07df0
+ 		/*
e07df0
+		 * Limit the maximum number of simultaneous pipelined
e07df0
+		 * queries on TCP connection to TCP_CLIENTS_PER_CONN.
e07df0
+ 		 */
e07df0
+		if ((isc_refcount_current(&client->tcpconn->clients)
e07df0
+			    > TCP_CLIENTS_PER_CONN))
e07df0
+		{
e07df0
+ 			client->tcpconn->pipelined = ISC_FALSE;
e07df0
+ 		}
e07df0
+
e07df0
+		if (client->tcpconn->pipelined) {
e07df0
+			/*
e07df0
+			 * We're pipelining. Replace the client; the
e07df0
+			 * replacement can read the TCP socket looking
e07df0
+			 * for new messages and this one can process the
e07df0
+			 * current message asynchronously.
e07df0
+			 *
e07df0
+			 * There will now be at least three clients using this
e07df0
+			 * TCP socket - one accepting new connections,
e07df0
+			 * one reading an existing connection to get new
e07df0
+			 * messages, and one answering the message already
e07df0
+			 * received.
e07df0
+			 */
e07df0
+			result = ns_client_replace(client);
e07df0
+			if (result != ISC_R_SUCCESS) {
e07df0
+				client->tcpconn->pipelined = ISC_FALSE;
e07df0
+			}
e07df0
+		}
e07df0
 	}
e07df0
 
e07df0
 	dns_opcodestats_increment(ns_g_server->opcodestats,
e07df0
diff --git a/bin/named/include/named/client.h b/bin/named/include/named/client.h
e07df0
index 0f54d2267b..86437ade22 100644
e07df0
--- a/bin/named/include/named/client.h
e07df0
+++ b/bin/named/include/named/client.h
e07df0
@@ -77,7 +77,10 @@
e07df0
 
e07df0
 /*% reference-counted TCP connection object */
e07df0
 typedef struct ns_tcpconn {
e07df0
-	isc_refcount_t		refs;
e07df0
+	isc_refcount_t		clients;	/* Number of clients using
e07df0
+						 * this connection. Conn can
e07df0
+						 * be freed if goes to 0
e07df0
+						 */
e07df0
 	isc_quota_t		*tcpquota;
e07df0
 	isc_boolean_t		pipelined;
e07df0
 } ns_tcpconn_t;
e07df0
-- 
e07df0
2.20.1
e07df0