djdelorie / rpms / glibc

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