resolv: Set a timeout for TCP-based query read (bug 19643)

Message ID 20220729085159.68458-1-sinan.lin@linux.alibaba.com
State New
Headers
Series resolv: Set a timeout for TCP-based query read (bug 19643) |

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent
dj/TryBot-32bit success Build for i686

Commit Message

Sinan July 29, 2022, 8:51 a.m. UTC
  There was no timeout set to tcp socket read during name resolution, and this
might lead to a hang on read when DNS response is too large. This patch proposes
a default 10 seconds timeout for TCP-based query read, and it currently does not
include the connection and write part.

Signed-off-by: Sinan Lin <sinan.lin@linux.alibaba.com>
---
 inet/deadline.c     | 15 +++++++++++++
 inet/net-internal.h |  5 +++++
 resolv/res_send.c   | 55 ++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 70 insertions(+), 5 deletions(-)
  

Patch

diff --git a/inet/deadline.c b/inet/deadline.c
index 7deb39b0ec..138e7a3351 100644
--- a/inet/deadline.c
+++ b/inet/deadline.c
@@ -73,6 +73,21 @@  __deadline_from_timeval (struct deadline_current_time current,
   return (struct deadline) { { sec, nsec } };
 }
 
+struct deadline
+__deadline_from_sec (struct deadline_current_time current, int sec)
+{
+  assert (sec >= 0);
+
+  /* Compute second-based deadline.  Perform the addition in
+     uintmax_t, which is unsigned, to simply overflow detection.  */
+  uintmax_t deadline_sec = current.current.tv_sec;
+  deadline_sec += sec;
+  if (deadline_sec < (uintmax_t) sec)
+    return infinite_deadline ();
+
+  return (struct deadline) { { deadline_sec, 0 } };
+}
+
 int
 __deadline_to_ms (struct deadline_current_time current,
                   struct deadline deadline)
diff --git a/inet/net-internal.h b/inet/net-internal.h
index cdccdd3976..638a3dbd53 100644
--- a/inet/net-internal.h
+++ b/inet/net-internal.h
@@ -139,6 +139,11 @@  DIAG_POP_NEEDS_COMMENT;
 struct deadline __deadline_from_timeval (struct deadline_current_time,
                                          struct timeval tv) attribute_hidden;
 
+/* Add time in second to the current time and return it.  Returns a special
+   infinite absolute deadline on overflow.  */
+struct deadline __deadline_from_sec (struct deadline_current_time,
+                                     int sec) attribute_hidden;
+
 /* Compute the number of milliseconds until the specified deadline,
    from the current time in the argument.  The result is mainly for
    use with poll.  If the deadline has already passed, return 0.  If
diff --git a/resolv/res_send.c b/resolv/res_send.c
index 6a08e729a4..fee32451aa 100644
--- a/resolv/res_send.c
+++ b/resolv/res_send.c
@@ -110,6 +110,7 @@ 
 #include <kernel-features.h>
 #include <libc-diag.h>
 #include <random-bits.h>
+#include <inet/net-internal.h>
 
 #if PACKETSZ > 65536
 #define MAXPACKET       PACKETSZ
@@ -190,6 +191,30 @@  static int		send_dg(res_state, const u_char *, int,
 				int *, int *, u_char **,
 				u_char **, int *, int *, int *);
 static int		sock_eq(struct sockaddr_in6 *, struct sockaddr_in6 *);
+static int		wait_on_socket(struct pollfd *, struct deadline);
+
+static int
+wait_on_socket (struct pollfd *pfd, struct deadline wait_deadline)
+{
+  do
+    {
+      int timeout = __deadline_to_ms (__deadline_current_time(),
+				      wait_deadline);
+      switch (__poll (pfd, 1, timeout))
+	{
+	case -1:
+	  if (errno == EINTR)
+	    continue;
+	  /* FALLTHROUGH */
+	case 0:
+	  return -1;
+	default:
+	  break;
+	}
+  } while ((pfd->revents & POLLIN) == 0);
+
+  return 1;
+}
 
 /* Returns a shift value for the name server index.  Used to implement
    RES_ROTATE.  */
@@ -570,6 +595,8 @@  send_vc(res_state statp,
 	UHEADER *anhp = (UHEADER *) *ansp;
 	struct sockaddr *nsap = __res_get_nsaddr (statp, ns);
 	int truncating, connreset, n;
+	struct pollfd pfd[1] = { [0] = { .events = POLLIN } };
+	struct deadline read_deadline;
 	/* On some architectures compiler might emit a warning indicating
 	   'resplen' may be used uninitialized.  However if buf2 == NULL
 	   then this code won't be executed; if buf2 != NULL, then first
@@ -589,6 +616,16 @@  send_vc(res_state statp,
 	u_char *cp;
 
 	connreset = 0;
+
+	/* Compute and set timeout for each socket read. The timeout is
+	    currently twice the lengh of send_dg routine. */
+	int seconds = (statp->retrans << (ns+1));
+	if (ns > 0)
+		seconds /= statp->nscount;
+	if (seconds <= 0)
+		seconds = 1;
+	read_deadline =
+	    __deadline_from_sec (__deadline_current_time(), seconds);
  same_ns:
 	truncating = 0;
 
@@ -657,10 +694,13 @@  send_vc(res_state statp,
 	int recvresp2 = buf2 == NULL;
 	uint16_t rlen16;
  read_len:
+	pfd[0].fd = statp->_vcsock;
 	cp = (u_char *)&rlen16;
 	len = sizeof(rlen16);
-	while ((n = TEMP_FAILURE_RETRY (read(statp->_vcsock, cp,
-					     (int)len))) > 0) {
+
+	while ((n = wait_on_socket (pfd, read_deadline)) > 0
+	  && (n = TEMP_FAILURE_RETRY (read(statp->_vcsock, cp,
+					       (int)len))) > 0) {
 		cp += n;
 		if ((len -= n) <= 0)
 			break;
@@ -744,10 +784,14 @@  send_vc(res_state statp,
 	}
 
 	cp = *thisansp;
-	while (len != 0 && (n = read(statp->_vcsock, (char *)cp, (int)len)) > 0){
+
+	while (len != 0
+	    && (n = wait_on_socket (pfd, read_deadline)) > 0
+	    && (n = read(statp->_vcsock, (char *)cp, (int)len)) > 0) {
 		cp += n;
 		len -= n;
 	}
+
 	if (__glibc_unlikely (n <= 0))       {
 		*terrno = errno;
 		return close_and_return_error (statp, resplen2);
@@ -758,9 +802,10 @@  send_vc(res_state statp,
 		 */
 		anhp->tc = 1;
 		len = rlen - *thisanssizp;
-		while (len != 0) {
-			char junk[PACKETSZ];
 
+		while (len != 0
+		    && (n = wait_on_socket (pfd, read_deadline)) > 0) {
+			char junk[PACKETSZ];
 			n = read(statp->_vcsock, junk,
 				 (len > sizeof junk) ? sizeof junk : len);
 			if (n > 0)