From d7ff9a1c41bf0ba9773cb3adb08b48b9fd57c956 Mon Sep 17 00:00:00 2001
From: Mark Andrews <marka@isc.org>
Date: Sat, 27 Feb 2016 11:23:50 +1100
Subject: [PATCH] 4322.   [security]      Duplicate EDNS COOKIE options in a
 response could                         trigger an assertion failure.
 (CVE-2016-2088)                         [RT #41809]

(cherry picked from commit 455c0848f80a8acda27aad1466c72987cafaa029)
(cherry picked from commit 7cd300abd6ee8b8ee8730593daf742ba53f90bc3)

Upstream-Status: Backport
CVE: CVE-2016-2088
minor fixup to get to apply.

Signed-off-by: Armin Kuster <akuster@mvista.com>

---
 CHANGES            |  5 +++++
 bin/dig/dighost.c  |  9 +++++++++
 bin/named/client.c | 33 +++++++++++++++++++++++----------
 doc/arm/notes.xml  |  7 +++++++
 lib/dns/resolver.c | 14 +++++++++++++-
 5 files changed, 57 insertions(+), 11 deletions(-)

Index: bind-9.10.2-P4/CHANGES
===================================================================
--- bind-9.10.2-P4.orig/CHANGES
+++ bind-9.10.2-P4/CHANGES
@@ -1,3 +1,7 @@
+4322.  [security]      Duplicate EDNS COOKIE options in a response could
+                       trigger an assertion failure. (CVE-2016-2088)
+                       [RT #41809]
+
 4319.  [security]      Fix resolver assertion failure due to improper
                        DNAME handling when parsing fetch reply messages.
                        (CVE-2016-1286) [RT #41753]
Index: bind-9.10.2-P4/bin/dig/dighost.c
===================================================================
--- bind-9.10.2-P4.orig/bin/dig/dighost.c
+++ bind-9.10.2-P4/bin/dig/dighost.c
@@ -3349,6 +3349,7 @@ process_opt(dig_lookup_t *l, dns_message
 	isc_buffer_t optbuf;
 	isc_uint16_t optcode, optlen;
 	dns_rdataset_t *opt = msg->opt;
+	isc_boolean_t seen_cookie = ISC_FALSE;
 
 	result = dns_rdataset_first(opt);
 	if (result == ISC_R_SUCCESS) {
@@ -3360,8 +3361,16 @@ process_opt(dig_lookup_t *l, dns_message
 			optcode = isc_buffer_getuint16(&optbuf);
 			optlen = isc_buffer_getuint16(&optbuf);
 			switch (optcode) {
-			case DNS_OPT_SIT:
+ 			case DNS_OPT_SIT:
+                                /*
+                                 * Only process the first cookie option.
+                                 */
+                                if (seen_cookie) {
+                                        isc_buffer_forward(&optbuf, optlen);
+                                        break;
+                                }
 				process_sit(l, msg, &optbuf, optlen);
+                                seen_cookie = ISC_TRUE;
 				break;
 			default:
 				isc_buffer_forward(&optbuf, optlen);
Index: bind-9.10.2-P4/bin/named/client.c
===================================================================
--- bind-9.10.2-P4.orig/bin/named/client.c
+++ bind-9.10.2-P4/bin/named/client.c
@@ -121,7 +121,10 @@
  */
 #endif
 
-#define SIT_SIZE 24U /* 8 + 4 + 4 + 8 */
+#define COOKIE_SIZE 24U /* 8 + 4 + 4 + 8 */
+
+#define WANTNSID(x) (((x)->attributes & NS_CLIENTATTR_WANTNSID) != 0)
+#define WANTEXPIRE(x) (((x)->attributes & NS_CLIENTATTR_WANTEXPIRE) != 0)
 
 /*% nameserver client manager structure */
 struct ns_clientmgr {
@@ -1391,7 +1394,7 @@ ns_client_addopt(ns_client_t *client, dn
 {
 	char nsid[BUFSIZ], *nsidp;
 #ifdef ISC_PLATFORM_USESIT
-	unsigned char sit[SIT_SIZE];
+	unsigned char sit[COOKIE_SIZE];
 #endif
 	isc_result_t result;
 	dns_view_t *view;
@@ -1416,7 +1419,7 @@ ns_client_addopt(ns_client_t *client, dn
 	flags = client->extflags & DNS_MESSAGEEXTFLAG_REPLYPRESERVE;
 
 	/* Set EDNS options if applicable */
-	if ((client->attributes & NS_CLIENTATTR_WANTNSID) != 0 &&
+	if (WANTNSID(client) &&
 	    (ns_g_server->server_id != NULL ||
 	     ns_g_server->server_usehostname)) {
 		if (ns_g_server->server_usehostname) {
@@ -1449,7 +1452,7 @@ ns_client_addopt(ns_client_t *client, dn
 
 		INSIST(count < DNS_EDNSOPTIONS);
 		ednsopts[count].code = DNS_OPT_SIT;
-		ednsopts[count].length = SIT_SIZE;
+		ednsopts[count].length = COOKIE_SIZE;
 		ednsopts[count].value = sit;
 		count++;
 	}
@@ -1657,19 +1660,26 @@ compute_sit(ns_client_t *client, isc_uin
 
 static void
 process_sit(ns_client_t *client, isc_buffer_t *buf, size_t optlen) {
-	unsigned char dbuf[SIT_SIZE];
+	unsigned char dbuf[COOKIE_SIZE];
 	unsigned char *old;
 	isc_stdtime_t now;
 	isc_uint32_t when;
 	isc_uint32_t nonce;
 	isc_buffer_t db;
 
+	/*
+	 * If we have already seen a ECS option skip this ECS option.
+	 */
+	if ((client->attributes & NS_CLIENTATTR_WANTSIT) != 0) {
+		isc_buffer_forward(buf, optlen);
+		return;
+	}
 	client->attributes |= NS_CLIENTATTR_WANTSIT;
 
 	isc_stats_increment(ns_g_server->nsstats,
 			    dns_nsstatscounter_sitopt);
 
-	if (optlen != SIT_SIZE) {
+	if (optlen != COOKIE_SIZE) {
 		/*
 		 * Not our token.
 		 */
@@ -1713,7 +1723,7 @@ process_sit(ns_client_t *client, isc_buf
 	isc_buffer_init(&db, dbuf, sizeof(dbuf));
 	compute_sit(client, when, nonce, &db);
 
-	if (memcmp(old, dbuf, SIT_SIZE) != 0) {
+	if (memcmp(old, dbuf, COOKIE_SIZE) != 0) {
 		isc_stats_increment(ns_g_server->nsstats,
 				    dns_nsstatscounter_sitnomatch);
 		return;
@@ -1779,7 +1789,9 @@ process_opt(ns_client_t *client, dns_rda
 			optlen = isc_buffer_getuint16(&optbuf);
 			switch (optcode) {
 			case DNS_OPT_NSID:
-				isc_stats_increment(ns_g_server->nsstats,
+				if (!WANTNSID(client))
+					isc_stats_increment(
+						    ns_g_server->nsstats,
 						    dns_nsstatscounter_nsidopt);
 				client->attributes |= NS_CLIENTATTR_WANTNSID;
 				isc_buffer_forward(&optbuf, optlen);
@@ -1790,7 +1802,9 @@ process_opt(ns_client_t *client, dns_rda
 				break;
 #endif
 			case DNS_OPT_EXPIRE:
-				isc_stats_increment(ns_g_server->nsstats,
+				if (!WANTEXPIRE(client))
+					isc_stats_increment(
+						  ns_g_server->nsstats,
 						  dns_nsstatscounter_expireopt);
 				client->attributes |= NS_CLIENTATTR_WANTEXPIRE;
 				isc_buffer_forward(&optbuf, optlen);
Index: bind-9.10.2-P4/lib/dns/resolver.c
===================================================================
--- bind-9.10.2-P4.orig/lib/dns/resolver.c
+++ bind-9.10.2-P4/lib/dns/resolver.c
@@ -7144,7 +7144,9 @@ process_opt(resquery_t *query, dns_rdata
 	unsigned char *sit;
 	dns_adbaddrinfo_t *addrinfo;
 	unsigned char cookie[8];
+	isc_boolean_t seen_cookie = ISC_FALSE;
 #endif
+	isc_boolean_t seen_nsid = ISC_FALSE;
 
 	result = dns_rdataset_first(opt);
 	if (result == ISC_R_SUCCESS) {
@@ -7158,14 +7160,23 @@ process_opt(resquery_t *query, dns_rdata
 			INSIST(optlen <= isc_buffer_remaininglength(&optbuf));
 			switch (optcode) {
 			case DNS_OPT_NSID:
-				if (query->options & DNS_FETCHOPT_WANTNSID)
+				if (!seen_nsid &&
+                                    query->options & DNS_FETCHOPT_WANTNSID)
 					log_nsid(&optbuf, optlen, query,
 						 ISC_LOG_DEBUG(3),
 						 query->fctx->res->mctx);
 				isc_buffer_forward(&optbuf, optlen);
+	                        seen_nsid = ISC_TRUE;
 				break;
 #ifdef ISC_PLATFORM_USESIT
 			case DNS_OPT_SIT:
+                                /*
+                                 * Only process the first cookie option.
+                                 */
+                                if (seen_cookie) {
+                                        isc_buffer_forward(&optbuf, optlen);
+                                        break;
+                                }
 				sit = isc_buffer_current(&optbuf);
 				compute_cc(query, cookie, sizeof(cookie));
 				INSIST(query->fctx->rmessage->sitbad == 0 &&
@@ -7183,6 +7194,7 @@ process_opt(resquery_t *query, dns_rdata
 				isc_buffer_forward(&optbuf, optlen);
 				inc_stats(query->fctx->res,
 					  dns_resstatscounter_sitin);
+				seen_cookie = ISC_TRUE;
 				break;
 #endif
 			default:
