summaryrefslogtreecommitdiffstats
path: root/meta/recipes-connectivity/bind/bind/0004-better-tcpquota-accounting-and-client-mortality-chec.patch
diff options
context:
space:
mode:
Diffstat (limited to 'meta/recipes-connectivity/bind/bind/0004-better-tcpquota-accounting-and-client-mortality-chec.patch')
-rw-r--r--meta/recipes-connectivity/bind/bind/0004-better-tcpquota-accounting-and-client-mortality-chec.patch512
1 files changed, 512 insertions, 0 deletions
diff --git a/meta/recipes-connectivity/bind/bind/0004-better-tcpquota-accounting-and-client-mortality-chec.patch b/meta/recipes-connectivity/bind/bind/0004-better-tcpquota-accounting-and-client-mortality-chec.patch
new file mode 100644
index 0000000000..034ab13303
--- /dev/null
+++ b/meta/recipes-connectivity/bind/bind/0004-better-tcpquota-accounting-and-client-mortality-chec.patch
@@ -0,0 +1,512 @@
+Backport patch to fix CVE-2018-5743.
+
+Ref:
+https://security-tracker.debian.org/tracker/CVE-2018-5743
+
+CVE: CVE-2018-5743
+Upstream-Status: Backport [https://gitlab.isc.org/isc-projects/bind9/commit/2ab8a08]
+
+Signed-off-by: Kai Kang <kai.kang@windriver.com>
+
+From 2ab8a085b3c666f28f1f9229bd6ecb59915b26c3 Mon Sep 17 00:00:00 2001
+From: Evan Hunt <each@isc.org>
+Date: Fri, 5 Apr 2019 16:12:18 -0700
+Subject: [PATCH 4/6] 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)
+---
+ bin/named/client.c | 244 +++++++++++++++++++------------
+ bin/named/include/named/client.h | 3 +-
+ 2 files changed, 152 insertions(+), 95 deletions(-)
+
+diff --git a/bin/named/client.c b/bin/named/client.c
+index 277656cef0..61e96dd28c 100644
+--- a/bin/named/client.c
++++ b/bin/named/client.c
+@@ -244,13 +244,14 @@ static void client_start(isc_task_t *task, isc_event_t *event);
+ static void client_request(isc_task_t *task, isc_event_t *event);
+ static void ns_client_dumpmessage(ns_client_t *client, const char *reason);
+ static isc_result_t get_client(ns_clientmgr_t *manager, ns_interface_t *ifp,
+- dns_dispatch_t *disp, bool tcp);
++ dns_dispatch_t *disp, ns_client_t *oldclient,
++ bool tcp);
+ static isc_result_t get_worker(ns_clientmgr_t *manager, ns_interface_t *ifp,
+ isc_socket_t *sock, ns_client_t *oldclient);
+ static inline bool
+ allowed(isc_netaddr_t *addr, dns_name_t *signer,
+ isc_netaddr_t *ecs_addr, uint8_t ecs_addrlen,
+- uint8_t *ecs_scope, dns_acl_t *acl)
++ uint8_t *ecs_scope, dns_acl_t *acl);
+ static void compute_cookie(ns_client_t *client, uint32_t when,
+ uint32_t nonce, const unsigned char *secret,
+ isc_buffer_t *buf);
+@@ -319,7 +320,7 @@ pipeline_init(ns_client_t *client) {
+ * contention here is expected to be negligible, given that this code
+ * is only executed for TCP connections.
+ */
+- refs = isc_mem_allocate(client->sctx->mctx, sizeof(*refs));
++ refs = isc_mem_allocate(ns_g_mctx, sizeof(*refs));
+ isc_refcount_init(refs, 1);
+ client->pipeline_refs = refs;
+ }
+@@ -331,13 +332,13 @@ pipeline_init(ns_client_t *client) {
+ */
+ static void
+ pipeline_attach(ns_client_t *source, ns_client_t *target) {
+- int old_refs;
++ int refs;
+
+ REQUIRE(source->pipeline_refs != NULL);
+ REQUIRE(target->pipeline_refs == NULL);
+
+- old_refs = isc_refcount_increment(source->pipeline_refs);
+- INSIST(old_refs > 0);
++ isc_refcount_increment(source->pipeline_refs, &refs);
++ INSIST(refs > 1);
+ target->pipeline_refs = source->pipeline_refs;
+ }
+
+@@ -349,25 +350,51 @@ pipeline_attach(ns_client_t *source, ns_client_t *target) {
+ */
+ static bool
+ pipeline_detach(ns_client_t *client) {
+- isc_refcount_t *refs;
+- int old_refs;
++ isc_refcount_t *refcount;
++ int refs;
+
+ REQUIRE(client->pipeline_refs != NULL);
+
+- refs = client->pipeline_refs;
++ refcount = client->pipeline_refs;
+ client->pipeline_refs = NULL;
+
+- old_refs = isc_refcount_decrement(refs);
+- INSIST(old_refs > 0);
++ isc_refcount_decrement(refcount, refs);
+
+- if (old_refs == 1) {
+- isc_mem_free(client->sctx->mctx, refs);
++ if (refs == 0) {
++ isc_mem_free(ns_g_mctx, refs);
+ return (true);
+ }
+
+ return (false);
+ }
+
++/*
++ * Detach a client from the TCP client quota if appropriate, and set
++ * the quota pointer to NULL.
++ *
++ * Sometimes when the TCP client quota is exhausted but there are no other
++ * clients servicing the interface, a client will be allowed to continue
++ * running despite not having been attached to the quota. In this event,
++ * the TCP quota was never attached to the client, so when the client (or
++ * associated pipeline group) shuts down, the quota must NOT be detached.
++ *
++ * Otherwise, if the quota pointer is set, it should be detached. If not
++ * set at all, we just return without doing anything.
++ */
++static void
++tcpquota_disconnect(ns_client_t *client) {
++ if (client->tcpquota == NULL) {
++ return;
++ }
++
++ if (client->tcpattached) {
++ isc_quota_detach(&client->tcpquota);
++ client->tcpattached = false;
++ } else {
++ client->tcpquota = NULL;
++ }
++}
++
+ /*%
+ * Check for a deactivation or shutdown request and take appropriate
+ * action. Returns true if either is in progress; in this case
+@@ -490,38 +517,31 @@ exit_check(ns_client_t *client) {
+ client->tcpmsg_valid = false;
+ }
+
+- if (client->tcpquota != NULL) {
+- if (client->pipeline_refs == NULL ||
+- pipeline_detach(client))
+- {
+- /*
+- * Only detach from the TCP client quota if
+- * there are no more client structures using
+- * this TCP connection.
+- *
+- * Note that we check 'pipeline_refs' and not
+- * 'pipelined' because in some cases (e.g.
+- * after receiving a request with an opcode
+- * different than QUERY) 'pipelined' is set to
+- * false after the reference counter gets
+- * allocated in pipeline_init() and we must
+- * still drop our reference as failing to do so
+- * would prevent the reference counter itself
+- * from being freed.
+- */
+- isc_quota_detach(&client->tcpquota);
+- } else {
+- /*
+- * There are other client structures using this
+- * TCP connection, so we cannot detach from the
+- * TCP client quota to prevent excess TCP
+- * connections from being accepted. However,
+- * this client structure might later be reused
+- * for accepting new connections and thus must
+- * have its 'tcpquota' field set to NULL.
+- */
+- client->tcpquota = NULL;
+- }
++ /*
++ * Detach from pipeline group and from TCP client quota,
++ * if appropriate.
++ *
++ * - If no pipeline group is active, attempt to
++ * detach from the TCP client quota.
++ *
++ * - If a pipeline group is active, detach from it;
++ * if the return code indicates that there no more
++ * clients left if this pipeline group, we also detach
++ * from the TCP client quota.
++ *
++ * - Otherwise we don't try to detach, we just set the
++ * TCP quota pointer to NULL if it wasn't NULL already.
++ *
++ * tcpquota_disconnect() will set tcpquota to NULL, either
++ * by detaching it or by assignment, depending on the
++ * needs of the client. See the comments on that function
++ * for further information.
++ */
++ if (client->pipeline_refs == NULL || pipeline_detach(client)) {
++ tcpquota_disconnect(client);
++ } else {
++ client->tcpquota = NULL;
++ client->tcpattached = false;
+ }
+
+ if (client->tcpsocket != NULL) {
+@@ -544,8 +564,6 @@ exit_check(ns_client_t *client) {
+ client->timerset = false;
+ }
+
+- client->pipelined = false;
+-
+ client->peeraddr_valid = false;
+
+ client->state = NS_CLIENTSTATE_READY;
+@@ -558,18 +576,27 @@ exit_check(ns_client_t *client) {
+ * active and force it to go inactive if not.
+ *
+ * UDP clients go inactive at this point, but a TCP client
+- * will needs to remain active if no other clients are
+- * listening for TCP requests on this interface, to
+- * prevent this interface from going nonresponsive.
++ * may need to remain active and go into ready state if
++ * no other clients are available to listen for TCP
++ * requests on this interface or (in the case of pipelined
++ * clients) to read for additional messages on the current
++ * connection.
+ */
+ if (client->mortal && TCP_CLIENT(client) && !ns_g_clienttest) {
+ LOCK(&client->interface->lock);
+- if (client->interface->ntcpaccepting == 0) {
++ if ((client->interface->ntcpaccepting == 0 ||
++ (client->pipelined &&
++ client->interface->ntcpactive < 2)) &&
++ client->newstate != NS_CLIENTSTATE_FREED)
++ {
+ client->mortal = false;
++ client->newstate = NS_CLIENTSTATE_READY;
+ }
+ UNLOCK(&client->interface->lock);
+ }
+
++ client->pipelined = false;
++
+ /*
+ * We don't need the client; send it to the inactive
+ * queue for recycling.
+@@ -2634,6 +2661,18 @@ client_request(isc_task_t *task, isc_event_t *event) {
+ client->pipelined = false;
+ }
+ if (TCP_CLIENT(client) && client->pipelined) {
++ /*
++ * We're pipelining. Replace the client; the
++ * the replacement can read the TCP socket looking
++ * for new messages and this client can process the
++ * current message asynchronously.
++ *
++ * There are now at least three clients using this
++ * TCP socket - one accepting new connections,
++ * one reading an existing connection to get new
++ * messages, and one answering the message already
++ * received.
++ */
+ result = ns_client_replace(client);
+ if (result != ISC_R_SUCCESS) {
+ client->pipelined = false;
+@@ -3197,6 +3236,7 @@ client_create(ns_clientmgr_t *manager, ns_client_t **clientp) {
+ client->pipelined = false;
+ client->pipeline_refs = NULL;
+ client->tcpquota = NULL;
++ client->tcpattached = false;
+ client->recursionquota = NULL;
+ client->interface = NULL;
+ client->peeraddr_valid = false;
+@@ -3359,9 +3399,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));
+- if (client->tcpquota != NULL) {
+- isc_quota_detach(&client->tcpquota);
+- }
++ tcpquota_disconnect(client);
+ }
+
+ if (exit_check(client))
+@@ -3402,7 +3440,7 @@ client_newconn(isc_task_t *task, isc_event_t *event) {
+ client->pipelined = false;
+ result = ns_client_replace(client);
+ if (result == ISC_R_SUCCESS &&
+- (client->sctx->keepresporder == NULL ||
++ (ns_g_server->keepresporder == NULL ||
+ !allowed(&netaddr, NULL, NULL, 0, NULL,
+ ns_g_server->keepresporder)))
+ {
+@@ -3429,7 +3467,7 @@ client_accept(ns_client_t *client) {
+ * in named.conf. If we can't attach to it here, that means the TCP
+ * client quota has been exceeded.
+ */
+- result = isc_quota_attach(&client->sctx->tcpquota,
++ result = isc_quota_attach(&ns_g_server->tcpquota,
+ &client->tcpquota);
+ if (result != ISC_R_SUCCESS) {
+ bool exit;
+@@ -3447,27 +3485,27 @@ client_accept(ns_client_t *client) {
+ * interface to be starved, with no clients able
+ * to accept new connections.
+ *
+- * So, we check here to see if any other client
+- * is already servicing TCP queries on this
++ * So, we check here to see if any other clients
++ * are already servicing TCP queries on this
+ * interface (whether accepting, reading, or
+- * processing).
+- *
+- * If so, then it's okay *not* to call
+- * accept - we can let this client to go inactive
+- * and the other one handle the next connection
+- * when it's ready.
++ * processing). If there are at least two
++ * (one reading and one processing a request)
++ * then it's okay *not* to call accept - we
++ * can let this client go inactive and another
++ * one will resume accepting when it's done.
+ *
+- * But if not, then we need to be a little bit
+- * flexible about the quota. We allow *one* extra
+- * TCP client through, to ensure we're listening on
+- * every interface.
++ * 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.
+ *
+- * (Note: In practice this means that the *real*
+- * TCP client quota is tcp-clients plus the number
+- * of interfaces.)
++ * (Note: In practice this means that the real
++ * TCP client quota is tcp-clients plus the
++ * number of listening interfaces plus 2.)
+ */
+ LOCK(&client->interface->lock);
+- exit = (client->interface->ntcpactive > 0);
++ exit = (client->interface->ntcpactive > 1);
+ UNLOCK(&client->interface->lock);
+
+ if (exit) {
+@@ -3475,6 +3513,9 @@ client_accept(ns_client_t *client) {
+ (void)exit_check(client);
+ return;
+ }
++
++ } else {
++ client->tcpattached = true;
+ }
+
+ /*
+@@ -3507,9 +3548,16 @@ client_accept(ns_client_t *client) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "isc_socket_accept() failed: %s",
+ isc_result_totext(result));
+- if (client->tcpquota != NULL) {
+- isc_quota_detach(&client->tcpquota);
++
++ tcpquota_disconnect(client);
++
++ if (client->tcpactive) {
++ LOCK(&client->interface->lock);
++ client->interface->ntcpactive--;
++ UNLOCK(&client->interface->lock);
++ client->tcpactive = false;
+ }
++
+ return;
+ }
+
+@@ -3527,13 +3575,12 @@ client_accept(ns_client_t *client) {
+ * once the connection is established.
+ *
+ * When the client object is shutting down after handling a TCP
+- * request (see exit_check()), it looks to see whether this value is
+- * non-zero. If so, that means another client has already called
+- * accept() and is waiting to establish the next connection, which
+- * means the first client is free to go inactive. Otherwise,
+- * the first client must come back and call accept() again; this
+- * guarantees there will always be at least one client listening
+- * for new TCP connections on each interface.
++ * 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->ntcpaccepting++;
+@@ -3613,19 +3660,19 @@ ns_client_replace(ns_client_t *client) {
+ client->tcpsocket, client);
+ } else {
+ result = get_client(client->manager, client->interface,
+- client->dispatch, tcp);
++ client->dispatch, client, tcp);
++
++ /*
++ * The responsibility for listening for new requests is hereby
++ * transferred to the new client. Therefore, the old client
++ * should refrain from listening for any more requests.
++ */
++ client->mortal = true;
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+- /*
+- * The responsibility for listening for new requests is hereby
+- * transferred to the new client. Therefore, the old client
+- * should refrain from listening for any more requests.
+- */
+- client->mortal = true;
+-
+ return (ISC_R_SUCCESS);
+ }
+
+@@ -3759,7 +3806,7 @@ ns_clientmgr_destroy(ns_clientmgr_t **managerp) {
+
+ static isc_result_t
+ get_client(ns_clientmgr_t *manager, ns_interface_t *ifp,
+- dns_dispatch_t *disp, bool tcp)
++ dns_dispatch_t *disp, ns_client_t *oldclient, bool tcp)
+ {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_event_t *ev;
+@@ -3803,6 +3850,16 @@ get_client(ns_clientmgr_t *manager, ns_interface_t *ifp,
+ client->dscp = ifp->dscp;
+
+ if (tcp) {
++ client->tcpattached = false;
++ if (oldclient != NULL) {
++ client->tcpattached = oldclient->tcpattached;
++ }
++
++ LOCK(&client->interface->lock);
++ client->interface->ntcpactive++;
++ UNLOCK(&client->interface->lock);
++ client->tcpactive = true;
++
+ client->attributes |= NS_CLIENTATTR_TCP;
+ isc_socket_attach(ifp->tcpsocket,
+ &client->tcplistener);
+@@ -3866,7 +3923,8 @@ get_worker(ns_clientmgr_t *manager, ns_interface_t *ifp, isc_socket_t *sock,
+ ns_interface_attach(ifp, &client->interface);
+ client->newstate = client->state = NS_CLIENTSTATE_WORKING;
+ INSIST(client->recursionquota == NULL);
+- client->tcpquota = &client->sctx->tcpquota;
++ client->tcpquota = &ns_g_server->tcpquota;
++ client->tcpattached = oldclient->tcpattached;
+
+ client->dscp = ifp->dscp;
+
+@@ -3885,7 +3943,6 @@ get_worker(ns_clientmgr_t *manager, ns_interface_t *ifp, isc_socket_t *sock,
+ LOCK(&client->interface->lock);
+ client->interface->ntcpactive++;
+ UNLOCK(&client->interface->lock);
+-
+ client->tcpactive = true;
+
+ INSIST(client->tcpmsg_valid == false);
+@@ -3913,7 +3970,8 @@ ns_clientmgr_createclients(ns_clientmgr_t *manager, unsigned int n,
+ MTRACE("createclients");
+
+ for (disp = 0; disp < n; disp++) {
+- result = get_client(manager, ifp, ifp->udpdispatch[disp], tcp);
++ result = get_client(manager, ifp, ifp->udpdispatch[disp],
++ NULL, tcp);
+ if (result != ISC_R_SUCCESS)
+ break;
+ }
+diff --git a/bin/named/include/named/client.h b/bin/named/include/named/client.h
+index aeed9ccdda..e2c40acd28 100644
+--- a/bin/named/include/named/client.h
++++ b/bin/named/include/named/client.h
+@@ -9,8 +9,6 @@
+ * information regarding copyright ownership.
+ */
+
+-/* $Id: client.h,v 1.96 2012/01/31 23:47:31 tbox Exp $ */
+-
+ #ifndef NAMED_CLIENT_H
+ #define NAMED_CLIENT_H 1
+
+@@ -136,6 +134,7 @@ struct ns_client {
+ bool pipelined; /*%< TCP queries not in sequence */
+ isc_refcount_t *pipeline_refs;
+ isc_quota_t *tcpquota;
++ bool tcpattached;
+ isc_quota_t *recursionquota;
+ ns_interface_t *interface;
+
+--
+2.20.1
+