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