00db10
commit b66d837bb5398795c6b0f651bd5a5d66091d8577
00db10
Author: Florian Weimer <fweimer@redhat.com>
00db10
Date:   Fri Mar 25 11:49:51 2016 +0100
00db10
00db10
    resolv: Always set *resplen2 out parameter in send_dg [BZ #19791]
00db10
    
00db10
    Since commit 44d20bca52ace85850012b0ead37b360e3ecd96e (Implement
00db10
    second fallback mode for DNS requests), there is a code path which
00db10
    returns early, before *resplen2 is initialized.  This happens if the
00db10
    name server address is immediately recognized as invalid (because of
00db10
    lack of protocol support, or if it is a broadcast address such
00db10
    255.255.255.255, or another invalid address).
00db10
    
00db10
    If this happens and *resplen2 was non-zero (which is the case if a
00db10
    previous query resulted in a failure), __libc_res_nquery would reuse
00db10
    an existing second answer buffer.  This answer has been previously
00db10
    identified as unusable (for example, it could be an NXDOMAIN
00db10
    response).  Due to the presence of a second answer, no name server
00db10
    switching will occur.  The result is a name resolution failure,
00db10
    although a successful resolution would have been possible if name
00db10
    servers have been switched and queries had proceeded along the search
00db10
    path.
00db10
    
00db10
    The above paragraph still simplifies the situation.  Before glibc
00db10
    2.23, if the second answer needed malloc, the stub resolver would
00db10
    still attempt to reuse the second answer, but this is not possible
00db10
    because __libc_res_nsearch has freed it, after the unsuccessful call
00db10
    to __libc_res_nquerydomain, and set the buffer pointer to NULL.  This
00db10
    eventually leads to an assertion failure in __libc_res_nquery:
00db10
    
00db10
        /* Make sure both hp and hp2 are defined */
00db10
        assert((hp != NULL) && (hp2 != NULL));
00db10
    
00db10
    If assertions are disabled, the consequence is a NULL pointer
00db10
    dereference on the next line.
00db10
    
00db10
    Starting with glibc 2.23, as a result of commit
00db10
    e9db92d3acfe1822d56d11abcea5bfc4c41cf6ca (CVE-2015-7547: getaddrinfo()
00db10
    stack-based buffer overflow (Bug 18665)), the second answer is always
00db10
    allocated with malloc.  This means that the assertion failure happens
00db10
    with small responses as well because there is no buffer to reuse, as
00db10
    soon as there is a name resolution failure which triggers a search for
00db10
    an answer along the search path.
00db10
    
00db10
    This commit addresses the issue by ensuring that *resplen2 is
00db10
    initialized before the send_dg function returns.
00db10
    
00db10
    This commit also addresses a bug where an invalid second reply is
00db10
    incorrectly returned as a valid to the caller.
00db10
00db10
Index: glibc-2.17-c758a686/resolv/res_send.c
00db10
===================================================================
00db10
--- glibc-2.17-c758a686.orig/resolv/res_send.c
00db10
+++ glibc-2.17-c758a686/resolv/res_send.c
00db10
@@ -672,6 +672,18 @@ libresolv_hidden_def (res_nsend)
00db10
 
00db10
 /* Private */
00db10
 
00db10
+/* Close the resolver structure, assign zero to *RESPLEN2 if RESPLEN2
00db10
+   is not NULL, and return zero.  */
00db10
+static int
00db10
+__attribute__ ((warn_unused_result))
00db10
+close_and_return_error (res_state statp, int *resplen2)
00db10
+{
00db10
+  __res_iclose(statp, false);
00db10
+  if (resplen2 != NULL)
00db10
+    *resplen2 = 0;
00db10
+  return 0;
00db10
+}
00db10
+
00db10
 /* The send_vc function is responsible for sending a DNS query over TCP
00db10
    to the nameserver numbered NS from the res_state STATP i.e.
00db10
    EXT(statp).nssocks[ns].  The function supports sending both IPv4 and
00db10
@@ -1159,7 +1171,11 @@ send_dg(res_state statp,
00db10
  retry_reopen:
00db10
 	retval = reopen (statp, terrno, ns);
00db10
 	if (retval <= 0)
00db10
-		return retval;
00db10
+	  {
00db10
+	    if (resplen2 != NULL)
00db10
+	      *resplen2 = 0;
00db10
+	    return retval;
00db10
+	  }
00db10
  retry:
00db10
 	evNowTime(&now;;
00db10
 	evConsTime(&timeout, seconds, 0);
00db10
@@ -1172,8 +1188,6 @@ send_dg(res_state statp,
00db10
 	int recvresp2 = buf2 == NULL;
00db10
 	pfd[0].fd = EXT(statp).nssocks[ns];
00db10
 	pfd[0].events = POLLOUT;
00db10
-	if (resplen2 != NULL)
00db10
-	  *resplen2 = 0;
00db10
  wait:
00db10
 	if (need_recompute) {
00db10
 	recompute_resend:
00db10
@@ -1181,9 +1195,7 @@ send_dg(res_state statp,
00db10
 		if (evCmpTime(finish, now) <= 0) {
00db10
 		poll_err_out:
00db10
 			Perror(statp, stderr, "poll", errno);
00db10
-		err_out:
00db10
-			__res_iclose(statp, false);
00db10
-			return (0);
00db10
+			return close_and_return_error (statp, resplen2);
00db10
 		}
00db10
 		evSubTime(&timeout, &finish, &now;;
00db10
 		need_recompute = 0;
00db10
@@ -1230,7 +1242,9 @@ send_dg(res_state statp,
00db10
 		  }
00db10
 
00db10
 		*gotsomewhere = 1;
00db10
-		return (0);
00db10
+		if (resplen2 != NULL)
00db10
+		  *resplen2 = 0;
00db10
+		return 0;
00db10
 	}
00db10
 	if (n < 0) {
00db10
 		if (errno == EINTR)
00db10
@@ -1298,7 +1312,7 @@ send_dg(res_state statp,
00db10
 
00db10
 		      fail_sendmmsg:
00db10
 			Perror(statp, stderr, "sendmmsg", errno);
00db10
-			goto err_out;
00db10
+			return close_and_return_error (statp, resplen2);
00db10
 		      }
00db10
 		  }
00db10
 		else
00db10
@@ -1316,7 +1330,7 @@ send_dg(res_state statp,
00db10
 		      if (errno == EINTR || errno == EAGAIN)
00db10
 			goto recompute_resend;
00db10
 		      Perror(statp, stderr, "send", errno);
00db10
-		      goto err_out;
00db10
+		      return close_and_return_error (statp, resplen2);
00db10
 		    }
00db10
 		  just_one:
00db10
 		    if (nwritten != 0 || buf2 == NULL || single_request)
00db10
@@ -1392,7 +1406,7 @@ send_dg(res_state statp,
00db10
 				goto wait;
00db10
 			}
00db10
 			Perror(statp, stderr, "recvfrom", errno);
00db10
-			goto err_out;
00db10
+			return close_and_return_error (statp, resplen2);
00db10
 		}
00db10
 		*gotsomewhere = 1;
00db10
 		if (__builtin_expect (*thisresplenp < HFIXEDSZ, 0)) {
00db10
@@ -1403,7 +1417,7 @@ send_dg(res_state statp,
00db10
 			       (stdout, ";; undersized: %d\n",
00db10
 				*thisresplenp));
00db10
 			*terrno = EMSGSIZE;
00db10
-			goto err_out;
00db10
+			return close_and_return_error (statp, resplen2);
00db10
 		}
00db10
 		if ((recvresp1 || hp->id != anhp->id)
00db10
 		    && (recvresp2 || hp2->id != anhp->id)) {
00db10
@@ -1452,7 +1466,7 @@ send_dg(res_state statp,
00db10
 				? *thisanssizp : *thisresplenp);
00db10
 			/* record the error */
00db10
 			statp->_flags |= RES_F_EDNS0ERR;
00db10
-			goto err_out;
00db10
+			return close_and_return_error (statp, resplen2);
00db10
 	}
00db10
 #endif
00db10
 		if (!(statp->options & RES_INSECURE2)
00db10
@@ -1504,10 +1518,10 @@ send_dg(res_state statp,
00db10
 			    goto wait;
00db10
 			  }
00db10
 
00db10
-			__res_iclose(statp, false);
00db10
 			/* don't retry if called from dig */
00db10
 			if (!statp->pfcode)
00db10
-				return (0);
00db10
+			  return close_and_return_error (statp, resplen2);
00db10
+			__res_iclose(statp, false);
00db10
 		}
00db10
 		if (anhp->rcode == NOERROR && anhp->ancount == 0
00db10
 		    && anhp->aa == 0 && anhp->ra == 0 && anhp->arcount == 0) {
00db10
@@ -1529,6 +1543,8 @@ send_dg(res_state statp,
00db10
 			__res_iclose(statp, false);
00db10
 			// XXX if we have received one reply we could
00db10
 			// XXX use it and not repeat it over TCP...
00db10
+			if (resplen2 != NULL)
00db10
+			  *resplen2 = 0;
00db10
 			return (1);
00db10
 		}
00db10
 		/* Mark which reply we received.  */
00db10
@@ -1544,21 +1560,22 @@ send_dg(res_state statp,
00db10
 					__res_iclose (statp, false);
00db10
 					retval = reopen (statp, terrno, ns);
00db10
 					if (retval <= 0)
00db10
-						return retval;
00db10
+					  {
00db10
+					    if (resplen2 != NULL)
00db10
+					      *resplen2 = 0;
00db10
+					    return retval;
00db10
+					  }
00db10
 					pfd[0].fd = EXT(statp).nssocks[ns];
00db10
 				}
00db10
 			}
00db10
 			goto wait;
00db10
 		}
00db10
-		/*
00db10
-		 * All is well, or the error is fatal.  Signal that the
00db10
-		 * next nameserver ought not be tried.
00db10
-		 */
00db10
+		/* All is well.  We have received both responses (if
00db10
+		   two responses were requested).  */
00db10
 		return (resplen);
00db10
-	} else if (pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
00db10
-		/* Something went wrong.  We can stop trying.  */
00db10
-		goto err_out;
00db10
-	}
00db10
+	} else if (pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL))
00db10
+	  /* Something went wrong.  We can stop trying.  */
00db10
+	  return close_and_return_error (statp, resplen2);
00db10
 	else {
00db10
 		/* poll should not have returned > 0 in this case.  */
00db10
 		abort ();