Tizen 2.4 SDK Rev5 accepted/tizen/2.3.1/mobile/20160414.075900 accepted/tizen/2.3.1/wearable/20160414.080024 submit/tizen_2.3.1/20160414.052911
authorJaekyu Park <jk7744.park@samsung.com>
Tue, 12 Apr 2016 12:35:10 +0000 (21:35 +0900)
committerJaekyu Park <jk7744.park@samsung.com>
Tue, 12 Apr 2016 12:35:10 +0000 (21:35 +0900)
catgets/Makefile
catgets/catgets.c
catgets/open_catalog.c
catgets/tst-catgets.c
include/resolv.h
resolv/gethnamaddr.c
resolv/nss_dns/dns-canon.c
resolv/nss_dns/dns-host.c
resolv/nss_dns/dns-network.c
resolv/res_query.c
resolv/res_send.c

index e419803..64fb930 100644 (file)
@@ -50,15 +50,15 @@ catgets-CPPFLAGS := -DNLSPATH='"$(msgcatdir)/%L/%N:$(msgcatdir)/%L/LC_MESSAGES/%
 CPPFLAGS-gencat = -DNOT_IN_libc
 
 generated = de.msg test1.cat test1.h test2.cat test2.h sample.SJIS.cat \
-           test-gencat.h
+           test-gencat.h tst-catgets.mtrace tst-catgets-mem.out
 generated-dirs = de
 
-tst-catgets-ENV = NLSPATH="$(objpfx)%l/%N.cat" LANG=de
+tst-catgets-ENV = NLSPATH="$(objpfx)%l/%N.cat" LANG=de MALLOC_TRACE=$(objpfx)tst-catgets.mtrace
 
 # eglibc: ifneq ($(cross-compiling),yes)
 ifeq (y,$(OPTION_EGLIBC_CATGETS))
 tests: $(objpfx)de/libc.cat $(objpfx)test1.cat $(objpfx)test2.cat \
-       $(objpfx)test-gencat.out
+       $(objpfx)test-gencat.out $(objpfx)tst-catgets-mem.out
 endif
 # This test just checks whether the program produces any error or not.
 # The result is not tested.
@@ -87,4 +87,8 @@ $(objpfx)test-gencat.out: test-gencat.sh $(objpfx)test-gencat \
 $(objpfx)sample.SJIS.cat: sample.SJIS $(objpfx)gencat
        GCONV_PATH=$(common-objpfx)iconvdata LC_ALL=C \
        $(built-program-cmd) -H $(objpfx)test-gencat.h < $(word 1,$^) > $@
+
+$(objpfx)tst-catgets-mem.out: $(objpfx)tst-catgets.out
+       $(common-objpfx)malloc/mtrace $(objpfx)tst-catgets.mtrace > $@; \
+       $(evaluate-test)
 # eglibc: endif
index 9fd0115..630d93d 100644 (file)
@@ -17,7 +17,6 @@
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA.  */
 
-#include <alloca.h>
 #include <errno.h>
 #include <locale.h>
 #include <nl_types.h>
@@ -36,6 +35,7 @@ catopen (const char *cat_name, int flag)
   __nl_catd result;
   const char *env_var = NULL;
   const char *nlspath = NULL;
+  char *tmp = NULL;
 
   if (strchr (cat_name, '/') == NULL)
     {
@@ -55,7 +55,10 @@ catopen (const char *cat_name, int flag)
        {
          /* Append the system dependent directory.  */
          size_t len = strlen (nlspath) + 1 + sizeof NLSPATH;
-         char *tmp = alloca (len);
+         tmp = malloc (len);
+
+         if (__builtin_expect (tmp == NULL, 0))
+           return (nl_catd) -1;
 
          __stpcpy (__stpcpy (__stpcpy (tmp, nlspath), ":"), NLSPATH);
          nlspath = tmp;
@@ -66,16 +69,18 @@ catopen (const char *cat_name, int flag)
 
   result = (__nl_catd) malloc (sizeof (*result));
   if (result == NULL)
-    /* We cannot get enough memory.  */
-    return (nl_catd) -1;
-
-  if (__open_catalog (cat_name, nlspath, env_var, result) != 0)
+    {
+      /* We cannot get enough memory.  */
+      result = (nl_catd) -1;
+    }
+  else if (__open_catalog (cat_name, nlspath, env_var, result) != 0)
     {
       /* Couldn't open the file.  */
       free ((void *) result);
-      return (nl_catd) -1;
+      result = (nl_catd) -1;
     }
 
+  free (tmp);
   return (nl_catd) result;
 }
 
index 8c563b2..dbfcb84 100644 (file)
@@ -48,6 +48,7 @@ __open_catalog (const char *cat_name, const char *nlspath, const char *env_var,
   size_t tab_size;
   const char *lastp;
   int result = -1;
+  char *buf = NULL;
 
   if (strchr (cat_name, '/') != NULL || nlspath == NULL)
     fd = open_not_cancel_2 (cat_name, O_RDONLY);
@@ -58,23 +59,23 @@ __open_catalog (const char *cat_name, const char *nlspath, const char *env_var,
   if (__builtin_expect (bufact + (n) >= bufmax, 0))                          \
     {                                                                        \
       char *old_buf = buf;                                                   \
-      bufmax += 256 + (n);                                                   \
-      buf = (char *) alloca (bufmax);                                        \
-      memcpy (buf, old_buf, bufact);                                         \
+      bufmax += (bufmax < 256 + (n)) ? 256 + (n) : bufmax;                   \
+      buf = realloc (buf, bufmax);                                           \
+      if (__builtin_expect (buf == NULL, 0))                                 \
+       {                                                                     \
+         free (old_buf);                                                     \
+         return -1;                                                          \
+       }                                                                     \
     }
 
       /* The RUN_NLSPATH variable contains a colon separated list of
         descriptions where we expect to find catalogs.  We have to
         recognize certain % substitutions and stop when we found the
         first existing file.  */
-      char *buf;
       size_t bufact;
-      size_t bufmax;
+      size_t bufmax = 0;
       size_t len;
 
-      buf = NULL;
-      bufmax = 0;
-
       fd = -1;
       while (*run_nlspath != '\0')
        {
@@ -189,7 +190,10 @@ __open_catalog (const char *cat_name, const char *nlspath, const char *env_var,
 
   /* Avoid dealing with directories and block devices */
   if (__builtin_expect (fd, 0) < 0)
-    return -1;
+    {
+      free (buf);
+      return -1;
+    }
 
   if (__builtin_expect (__fxstat64 (_STAT_VER, fd, &st), 0) < 0)
     goto close_unlock_return;
@@ -326,6 +330,7 @@ __open_catalog (const char *cat_name, const char *nlspath, const char *env_var,
   /* Release the lock again.  */
  close_unlock_return:
   close_not_cancel_no_status (fd);
+  free (buf);
 
   return result;
 }
index fdaa834..25ef056 100644 (file)
@@ -1,7 +1,10 @@
+#include <assert.h>
 #include <mcheck.h>
 #include <nl_types.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
+#include <sys/resource.h>
 
 
 static const char *msgs[] =
@@ -12,6 +15,33 @@ static const char *msgs[] =
 };
 #define nmsgs (sizeof (msgs) / sizeof (msgs[0]))
 
+
+/* Test for unbounded alloca.  */
+static int
+do_bz17905 (void)
+{
+  char *buf;
+  struct rlimit rl;
+  nl_catd result;
+
+  const int sz = 1024 * 1024;
+
+  getrlimit (RLIMIT_STACK, &rl);
+  rl.rlim_cur = sz;
+  setrlimit (RLIMIT_STACK, &rl);
+
+  buf = malloc (sz + 1);
+  memset (buf, 'A', sz);
+  buf[sz] = '\0';
+  setenv ("NLSPATH", buf, 1);
+
+  result = catopen (buf, NL_CAT_LOCALE);
+  assert (result == (nl_catd) -1);
+
+  free (buf);
+  return 0;
+}
+
 #define ROUNDS 5
 
 int
@@ -62,5 +92,6 @@ main (void)
        }
     }
 
+  result += do_bz17905 ();
   return result;
 }
index 7ab7f87..1a33517 100644 (file)
@@ -58,11 +58,11 @@ libc_hidden_proto (__res_randomid)
 libc_hidden_proto (__res_state)
 
 int __libc_res_nquery (res_state, const char *, int, int, u_char *, int,
-                      u_char **, u_char **, int *, int *);
+                      u_char **, u_char **, int *, int *, int *);
 int __libc_res_nsearch (res_state, const char *, int, int, u_char *, int,
-                       u_char **, u_char **, int *, int *);
+                       u_char **, u_char **, int *, int *, int *);
 int __libc_res_nsend (res_state, const u_char *, int, const u_char *, int,
-                     u_char *, int, u_char **, u_char **, int *, int *)
+                     u_char *, int, u_char **, u_char **, int *, int *, int *)
   attribute_hidden;
 
 libresolv_hidden_proto (_sethtent)
index 5cf660a..6fb109e 100644 (file)
@@ -621,7 +621,7 @@ gethostbyname2(name, af)
        buf.buf = origbuf = (querybuf *) alloca (1024);
 
        if ((n = __libc_res_nsearch(&_res, name, C_IN, type, buf.buf->buf, 1024,
-                                   &buf.ptr, NULL, NULL, NULL)) < 0) {
+                                   &buf.ptr, NULL, NULL, NULL, NULL)) < 0) {
                if (buf.buf != origbuf)
                        free (buf.buf);
                Dprintf("res_nsearch failed (%d)\n", n);
@@ -716,12 +716,12 @@ gethostbyaddr(addr, len, af)
        buf.buf = orig_buf = (querybuf *) alloca (1024);
 
        n = __libc_res_nquery(&_res, qbuf, C_IN, T_PTR, buf.buf->buf, 1024,
-                             &buf.ptr, NULL, NULL, NULL);
+                             &buf.ptr, NULL, NULL, NULL, NULL);
        if (n < 0 && af == AF_INET6 && (_res.options & RES_NOIP6DOTINT) == 0) {
                strcpy(qp, "ip6.int");
                n = __libc_res_nquery(&_res, qbuf, C_IN, T_PTR, buf.buf->buf,
                                      buf.buf != orig_buf ? MAXPACKET : 1024,
-                                     &buf.ptr, NULL, NULL, NULL);
+                                     &buf.ptr, NULL, NULL, NULL, NULL);
        }
        if (n < 0) {
                if (buf.buf != orig_buf)
index 50a0fc0..7dd73a3 100644 (file)
@@ -62,7 +62,7 @@ _nss_dns_getcanonname_r (const char *name, char *buffer, size_t buflen,
     {
       int r = __libc_res_nquery (&_res, name, ns_c_in, qtypes[i],
                                 buf, sizeof (buf), &ansp.ptr, NULL, NULL,
-                                NULL);
+                                NULL, NULL);
       if (r > 0)
        {
          /* We need to decode the response.  Just one question record.
index fe4ac2d..077e3ce 100644 (file)
@@ -195,7 +195,7 @@ _nss_dns_gethostbyname3_r (const char *name, int af, struct hostent *result,
   host_buffer.buf = orig_host_buffer = (querybuf *) alloca (1024);
 
   n = __libc_res_nsearch (&_res, name, C_IN, type, host_buffer.buf->buf,
-                         1024, &host_buffer.ptr, NULL, NULL, NULL);
+                         1024, &host_buffer.ptr, NULL, NULL, NULL, NULL);
   if (n < 0)
     {
       switch (errno)
@@ -225,7 +225,7 @@ _nss_dns_gethostbyname3_r (const char *name, int af, struct hostent *result,
        n = __libc_res_nsearch (&_res, name, C_IN, T_A, host_buffer.buf->buf,
                                host_buffer.buf != orig_host_buffer
                                ? MAXPACKET : 1024, &host_buffer.ptr,
-                               NULL, NULL, NULL);
+                               NULL, NULL, NULL, NULL);
 
       if (n < 0)
        {
@@ -308,12 +308,13 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
   u_char *ans2p = NULL;
   int nans2p = 0;
   int resplen2 = 0;
+  int ans2p_malloced = 0;
 
   int olderr = errno;
   enum nss_status status;
   int n = __libc_res_nsearch (&_res, name, C_IN, T_UNSPEC,
                              host_buffer.buf->buf, 2048, &host_buffer.ptr,
-                             &ans2p, &nans2p, &resplen2);
+                             &ans2p, &nans2p, &resplen2, &ans2p_malloced);
   if (n < 0)
     {
       if (errno == ESRCH)
@@ -340,6 +341,10 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
                          resplen2, name, pat, buffer, buflen,
                          errnop, herrnop, ttlp);
 
+  /* Check whether ans2p was separately allocated.  */
+  if (ans2p_malloced)
+    free (ans2p);
+
   if (host_buffer.buf != orig_host_buffer)
     free (host_buffer.buf);
 
@@ -448,7 +453,7 @@ _nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af,
          strcpy (qp, "].ip6.arpa");
          n = __libc_res_nquery (&_res, qbuf, C_IN, T_PTR,
                                 host_buffer.buf->buf, 1024, &host_buffer.ptr,
-                                NULL, NULL, NULL);
+                                NULL, NULL, NULL, NULL);
          if (n >= 0)
            goto got_it_already;
        }
@@ -469,14 +474,14 @@ _nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af,
     }
 
   n = __libc_res_nquery (&_res, qbuf, C_IN, T_PTR, host_buffer.buf->buf,
-                        1024, &host_buffer.ptr, NULL, NULL, NULL);
+                        1024, &host_buffer.ptr, NULL, NULL, NULL, NULL);
   if (n < 0 && af == AF_INET6 && (_res.options & RES_NOIP6DOTINT) == 0)
     {
       strcpy (qp, "ip6.int");
       n = __libc_res_nquery (&_res, qbuf, C_IN, T_PTR, host_buffer.buf->buf,
                             host_buffer.buf != orig_host_buffer
                             ? MAXPACKET : 1024, &host_buffer.ptr,
-                            NULL, NULL, NULL);
+                            NULL, NULL, NULL, NULL);
     }
   if (n < 0)
     {
@@ -1030,7 +1035,10 @@ gaih_getanswer_slice (const querybuf *answer, int anslen, const char *qname,
   int h_namelen = 0;
 
   if (ancount == 0)
-    return NSS_STATUS_NOTFOUND;
+    {
+      *h_errnop = HOST_NOT_FOUND;
+      return NSS_STATUS_NOTFOUND;
+    }
 
   while (ancount-- > 0 && cp < end_of_message && had_error == 0)
     {
@@ -1199,7 +1207,14 @@ gaih_getanswer_slice (const querybuf *answer, int anslen, const char *qname,
   /* Special case here: if the resolver sent a result but it only
      contains a CNAME while we are looking for a T_A or T_AAAA record,
      we fail with NOTFOUND instead of TRYAGAIN.  */
-  return canon == NULL ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND;
+  if (canon != NULL)
+    {
+      *h_errnop = HOST_NOT_FOUND;
+      return NSS_STATUS_NOTFOUND;
+    }
+
+  *h_errnop = NETDB_INTERNAL;
+  return NSS_STATUS_TRYAGAIN;
 }
 
 
@@ -1213,11 +1228,101 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
 
   enum nss_status status = NSS_STATUS_NOTFOUND;
 
+  /* Combining the NSS status of two distinct queries requires some
+     compromise and attention to symmetry (A or AAAA queries can be
+     returned in any order).  What follows is a breakdown of how this
+     code is expected to work and why. We discuss only SUCCESS,
+     TRYAGAIN, NOTFOUND and UNAVAIL, since they are the only returns
+     that apply (though RETURN and MERGE exist).  We make a distinction
+     between TRYAGAIN (recoverable) and TRYAGAIN' (not-recoverable).
+     A recoverable TRYAGAIN is almost always due to buffer size issues
+     and returns ERANGE in errno and the caller is expected to retry
+     with a larger buffer.
+
+     Lastly, you may be tempted to make significant changes to the
+     conditions in this code to bring about symmetry between responses.
+     Please don't change anything without due consideration for
+     expected application behaviour.  Some of the synthesized responses
+     aren't very well thought out and sometimes appear to imply that
+     IPv4 responses are always answer 1, and IPv6 responses are always
+     answer 2, but that's not true (see the implemetnation of send_dg
+     and send_vc to see response can arrive in any order, particlarly
+     for UDP). However, we expect it holds roughly enough of the time
+     that this code works, but certainly needs to be fixed to make this
+     a more robust implementation.
+
+     ----------------------------------------------
+     | Answer 1 Status /   | Synthesized | Reason |
+     | Answer 2 Status     | Status      |        |
+     |--------------------------------------------|
+     | SUCCESS/SUCCESS     | SUCCESS     | [1]    |
+     | SUCCESS/TRYAGAIN    | TRYAGAIN    | [5]    |
+     | SUCCESS/TRYAGAIN'   | SUCCESS     | [1]    |
+     | SUCCESS/NOTFOUND    | SUCCESS     | [1]    |
+     | SUCCESS/UNAVAIL     | SUCCESS     | [1]    |
+     | TRYAGAIN/SUCCESS    | TRYAGAIN    | [2]    |
+     | TRYAGAIN/TRYAGAIN   | TRYAGAIN    | [2]    |
+     | TRYAGAIN/TRYAGAIN'  | TRYAGAIN    | [2]    |
+     | TRYAGAIN/NOTFOUND   | TRYAGAIN    | [2]    |
+     | TRYAGAIN/UNAVAIL    | TRYAGAIN    | [2]    |
+     | TRYAGAIN'/SUCCESS   | SUCCESS     | [3]    |
+     | TRYAGAIN'/TRYAGAIN  | TRYAGAIN    | [3]    |
+     | TRYAGAIN'/TRYAGAIN' | TRYAGAIN'   | [3]    |
+     | TRYAGAIN'/NOTFOUND  | TRYAGAIN'   | [3]    |
+     | TRYAGAIN'/UNAVAIL   | UNAVAIL     | [3]    |
+     | NOTFOUND/SUCCESS    | SUCCESS     | [3]    |
+     | NOTFOUND/TRYAGAIN   | TRYAGAIN    | [3]    |
+     | NOTFOUND/TRYAGAIN'  | TRYAGAIN'   | [3]    |
+     | NOTFOUND/NOTFOUND   | NOTFOUND    | [3]    |
+     | NOTFOUND/UNAVAIL    | UNAVAIL     | [3]    |
+     | UNAVAIL/SUCCESS     | UNAVAIL     | [4]    |
+     | UNAVAIL/TRYAGAIN    | UNAVAIL     | [4]    |
+     | UNAVAIL/TRYAGAIN'   | UNAVAIL     | [4]    |
+     | UNAVAIL/NOTFOUND    | UNAVAIL     | [4]    |
+     | UNAVAIL/UNAVAIL     | UNAVAIL     | [4]    |
+     ----------------------------------------------
+
+     [1] If the first response is a success we return success.
+         This ignores the state of the second answer and in fact
+         incorrectly sets errno and h_errno to that of the second
+        answer.  However because the response is a success we ignore
+        *errnop and *h_errnop (though that means you touched errno on
+         success).  We are being conservative here and returning the
+         likely IPv4 response in the first answer as a success.
+
+     [2] If the first response is a recoverable TRYAGAIN we return
+        that instead of looking at the second response.  The
+        expectation here is that we have failed to get an IPv4 response
+        and should retry both queries.
+
+     [3] If the first response was not a SUCCESS and the second
+        response is not NOTFOUND (had a SUCCESS, need to TRYAGAIN,
+        or failed entirely e.g. TRYAGAIN' and UNAVAIL) then use the
+        result from the second response, otherwise the first responses
+        status is used.  Again we have some odd side-effects when the
+        second response is NOTFOUND because we overwrite *errnop and
+        *h_errnop that means that a first answer of NOTFOUND might see
+        its *errnop and *h_errnop values altered.  Whether it matters
+        in practice that a first response NOTFOUND has the wrong
+        *errnop and *h_errnop is undecided.
+
+     [4] If the first response is UNAVAIL we return that instead of
+        looking at the second response.  The expectation here is that
+        it will have failed similarly e.g. configuration failure.
+
+     [5] Testing this code is complicated by the fact that truncated
+        second response buffers might be returned as SUCCESS if the
+        first answer is a SUCCESS.  To fix this we add symmetry to
+        TRYAGAIN with the second response.  If the second response
+        is a recoverable error we now return TRYAGIN even if the first
+        response was SUCCESS.  */
+
   if (anslen1 > 0)
     status = gaih_getanswer_slice(answer1, anslen1, qname,
                                  &pat, &buffer, &buflen,
                                  errnop, h_errnop, ttlp,
                                  &first);
+
   if ((status == NSS_STATUS_SUCCESS || status == NSS_STATUS_NOTFOUND
        || (status == NSS_STATUS_TRYAGAIN
           && (errno != ERANGE || *h_errnop != NO_RECOVERY)))
@@ -1227,8 +1332,15 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
                                                     &pat, &buffer, &buflen,
                                                     errnop, h_errnop, ttlp,
                                                     &first);
+      /* Use the second response status in some cases.  */
       if (status != NSS_STATUS_SUCCESS && status2 != NSS_STATUS_NOTFOUND)
        status = status2;
+      /* Do not return a truncated second response (unless it was
+         unavoidable e.g. unrecoverable TRYAGAIN).  */
+      if (status == NSS_STATUS_SUCCESS
+         && (status2 == NSS_STATUS_TRYAGAIN
+             && *errnop == ERANGE && *h_errnop != NO_RECOVERY))
+       status = NSS_STATUS_TRYAGAIN;
     }
 
   return status;
index c9969e0..f8338fa 100644 (file)
@@ -130,7 +130,7 @@ _nss_dns_getnetbyname_r (const char *name, struct netent *result,
   net_buffer.buf = orig_net_buffer = (querybuf *) alloca (1024);
 
   anslen = __libc_res_nsearch (&_res, qbuf, C_IN, T_PTR, net_buffer.buf->buf,
-                              1024, &net_buffer.ptr, NULL, NULL, NULL);
+                              1024, &net_buffer.ptr, NULL, NULL, NULL, NULL);
   if (anslen < 0)
     {
       /* Nothing found.  */
@@ -206,7 +206,7 @@ _nss_dns_getnetbyaddr_r (uint32_t net, int type, struct netent *result,
   net_buffer.buf = orig_net_buffer = (querybuf *) alloca (1024);
 
   anslen = __libc_res_nquery (&_res, qbuf, C_IN, T_PTR, net_buffer.buf->buf,
-                             1024, &net_buffer.ptr, NULL, NULL, NULL);
+                             1024, &net_buffer.ptr, NULL, NULL, NULL, NULL);
   if (anslen < 0)
     {
       /* Nothing found.  */
index 5ff352e..135ad53 100644 (file)
@@ -98,7 +98,7 @@ static int
 __libc_res_nquerydomain(res_state statp, const char *name, const char *domain,
                        int class, int type, u_char *answer, int anslen,
                        u_char **answerp, u_char **answerp2, int *nanswerp2,
-                       int *resplen2);
+                       int *resplen2, int *answerp2_malloced);
 
 /*
  * Formulate a normal query, send, and await answer.
@@ -119,7 +119,8 @@ __libc_res_nquery(res_state statp,
                  u_char **answerp,     /* if buffer needs to be enlarged */
                  u_char **answerp2,
                  int *nanswerp2,
-                 int *resplen2)
+                 int *resplen2,
+                 int *answerp2_malloced)
 {
        HEADER *hp = (HEADER *) answer;
        int n, use_malloc = 0;
@@ -223,7 +224,8 @@ __libc_res_nquery(res_state statp,
        }
        assert (answerp == NULL || (void *) *answerp == (void *) answer);
        n = __libc_res_nsend(statp, query1, nquery1, query2, nquery2, answer,
-                            anslen, answerp, answerp2, nanswerp2, resplen2);
+                            anslen, answerp, answerp2, nanswerp2, resplen2,
+                            answerp2_malloced);
        if (use_malloc)
                free (buf);
        if (n < 0) {
@@ -316,7 +318,7 @@ res_nquery(res_state statp,
           int anslen)          /* size of answer buffer */
 {
        return __libc_res_nquery(statp, name, class, type, answer, anslen,
-                                NULL, NULL, NULL, NULL);
+                                NULL, NULL, NULL, NULL, NULL);
 }
 libresolv_hidden_def (res_nquery)
 
@@ -335,7 +337,8 @@ __libc_res_nsearch(res_state statp,
                   u_char **answerp,
                   u_char **answerp2,
                   int *nanswerp2,
-                  int *resplen2)
+                  int *resplen2,
+                  int *answerp2_malloced)
 {
        const char *cp, * const *domain;
        HEADER *hp = (HEADER *) answer;
@@ -359,7 +362,7 @@ __libc_res_nsearch(res_state statp,
        if (!dots && (cp = res_hostalias(statp, name, tmp, sizeof tmp))!= NULL)
                return (__libc_res_nquery(statp, cp, class, type, answer,
                                          anslen, answerp, answerp2,
-                                         nanswerp2, resplen2));
+                                         nanswerp2, resplen2, answerp2_malloced));
 
 #ifdef DEBUG
        if (statp->options & RES_DEBUG)
@@ -376,8 +379,9 @@ __libc_res_nsearch(res_state statp,
        if (dots >= statp->ndots || trailing_dot) {
                ret = __libc_res_nquerydomain(statp, name, NULL, class, type,
                                              answer, anslen, answerp,
-                                             answerp2, nanswerp2, resplen2);
-               if (ret > 0 || trailing_dot)
+                                             answerp2, nanswerp2, resplen2,
+                                             answerp2_malloced);
+               if (ret > 0 || (ret == 0 && *resplen2 > 0) || trailing_dot)
                        return (ret);
                saved_herrno = h_errno;
                tried_as_is++;
@@ -385,11 +389,12 @@ __libc_res_nsearch(res_state statp,
                        answer = *answerp;
                        anslen = MAXPACKET;
                }
-               if (answerp2
-                   && (*answerp2 < answer || *answerp2 >= answer + anslen))
+               if (answerp2 && *answerp2_malloced)
                  {
                    free (*answerp2);
                    *answerp2 = NULL;
+                   *nanswerp2 = 0;
+                   *answerp2_malloced = 0;
                  }
        }
 
@@ -415,20 +420,20 @@ __libc_res_nsearch(res_state statp,
                                                      class, type,
                                                      answer, anslen, answerp,
                                                      answerp2, nanswerp2,
-                                                     resplen2);
-                       if (ret > 0)
+                                                     resplen2, answerp2_malloced);
+                       if ((ret > 0) || (ret == 0 && *resplen2 > 0))
                                return (ret);
 
                        if (answerp && *answerp != answer) {
                                answer = *answerp;
                                anslen = MAXPACKET;
                        }
-                       if (answerp2
-                           && (*answerp2 < answer
-                               || *answerp2 >= answer + anslen))
+                       if (answerp2 && *answerp2_malloced)
                          {
                            free (*answerp2);
                            *answerp2 = NULL;
+                           *nanswerp2 = 0;
+                           *answerp2_malloced = 0;
                          }
 
                        /*
@@ -484,8 +489,9 @@ __libc_res_nsearch(res_state statp,
        if (dots && !(tried_as_is || root_on_list)) {
                ret = __libc_res_nquerydomain(statp, name, NULL, class, type,
                                              answer, anslen, answerp,
-                                             answerp2, nanswerp2, resplen2);
-               if (ret > 0)
+                                             answerp2, nanswerp2, resplen2,
+                                             answerp2_malloced);
+               if ((ret > 0) || (ret == 0 && *resplen2 > 0))
                        return (ret);
        }
 
@@ -496,10 +502,12 @@ __libc_res_nsearch(res_state statp,
         * else send back meaningless H_ERRNO, that being the one from
         * the last DNSRCH we did.
         */
-       if (answerp2 && (*answerp2 < answer || *answerp2 >= answer + anslen))
+       if (answerp2 && *answerp2_malloced)
          {
            free (*answerp2);
            *answerp2 = NULL;
+           *nanswerp2 = 0;
+           *answerp2_malloced = 0;
          }
        if (saved_herrno != -1)
                RES_SET_H_ERRNO(statp, saved_herrno);
@@ -519,7 +527,7 @@ res_nsearch(res_state statp,
            int anslen)         /* size of answer */
 {
        return __libc_res_nsearch(statp, name, class, type, answer,
-                                 anslen, NULL, NULL, NULL, NULL);
+                                 anslen, NULL, NULL, NULL, NULL, NULL);
 }
 libresolv_hidden_def (res_nsearch)
 
@@ -537,7 +545,8 @@ __libc_res_nquerydomain(res_state statp,
                        u_char **answerp,
                        u_char **answerp2,
                        int *nanswerp2,
-                       int *resplen2)
+                       int *resplen2,
+                       int *answerp2_malloced)
 {
        char nbuf[MAXDNAME];
        const char *longname = nbuf;
@@ -575,7 +584,7 @@ __libc_res_nquerydomain(res_state statp,
        }
        return (__libc_res_nquery(statp, longname, class, type, answer,
                                  anslen, answerp, answerp2, nanswerp2,
-                                 resplen2));
+                                 resplen2, answerp2_malloced));
 }
 
 int
@@ -587,7 +596,8 @@ res_nquerydomain(res_state statp,
            int anslen)         /* size of answer */
 {
        return __libc_res_nquerydomain(statp, name, domain, class, type,
-                                      answer, anslen, NULL, NULL, NULL, NULL);
+                                      answer, anslen, NULL, NULL, NULL, NULL,
+                                      NULL);
 }
 libresolv_hidden_def (res_nquerydomain)
 
index 845b658..716aa03 100644 (file)
@@ -1,3 +1,20 @@
+/* Copyright (C) 2016 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
 /*
  * Copyright (c) 1985, 1989, 1993
  *    The Regents of the University of California.  All rights reserved.
@@ -186,12 +203,12 @@ evNowTime(struct timespec *res) {
 static int             send_vc(res_state, const u_char *, int,
                                const u_char *, int,
                                u_char **, int *, int *, int, u_char **,
-                               u_char **, int *, int *);
+                               u_char **, int *, int *, int *);
 static int             send_dg(res_state, const u_char *, int,
                                const u_char *, int,
                                u_char **, int *, int *, int,
                                int *, int *, u_char **,
-                               u_char **, int *, int *);
+                               u_char **, int *, int *, int *);
 #ifdef DEBUG
 static void            Aerror(const res_state, FILE *, const char *, int,
                               const struct sockaddr *);
@@ -343,7 +360,7 @@ int
 __libc_res_nsend(res_state statp, const u_char *buf, int buflen,
                 const u_char *buf2, int buflen2,
                 u_char *ans, int anssiz, u_char **ansp, u_char **ansp2,
-                int *nansp2, int *resplen2)
+                int *nansp2, int *resplen2, int *ansp2_malloced)
 {
   int gotsomewhere, terrno, try, v_circuit, resplen, ns, n;
 
@@ -360,6 +377,8 @@ __libc_res_nsend(res_state statp, const u_char *buf, int buflen,
 #ifdef USE_HOOKS
        if (__builtin_expect (statp->qhook || statp->rhook, 0)) {
                if (anssiz < MAXPACKET && ansp) {
+                       /* Always allocate MAXPACKET, callers expect
+                          this specific size.  */
                        u_char *buf = malloc (MAXPACKET);
                        if (buf == NULL)
                                return (-1);
@@ -546,7 +565,8 @@ __libc_res_nsend(res_state statp, const u_char *buf, int buflen,
                        try = statp->retry;
                        n = send_vc(statp, buf, buflen, buf2, buflen2,
                                    &ans, &anssiz, &terrno,
-                                   ns, ansp, ansp2, nansp2, resplen2);
+                                   ns, ansp, ansp2, nansp2, resplen2,
+                                   ansp2_malloced);
                        if (n < 0)
                                return (-1);
                        if (n == 0)
@@ -556,7 +576,7 @@ __libc_res_nsend(res_state statp, const u_char *buf, int buflen,
                        n = send_dg(statp, buf, buflen, buf2, buflen2,
                                    &ans, &anssiz, &terrno,
                                    ns, &v_circuit, &gotsomewhere, ansp,
-                                   ansp2, nansp2, resplen2);
+                                   ansp2, nansp2, resplen2, ansp2_malloced);
                        if (n < 0)
                                return (-1);
                        if (n == 0)
@@ -646,26 +666,93 @@ res_nsend(res_state statp,
          const u_char *buf, int buflen, u_char *ans, int anssiz)
 {
   return __libc_res_nsend(statp, buf, buflen, NULL, 0, ans, anssiz,
-                         NULL, NULL, NULL, NULL);
+                         NULL, NULL, NULL, NULL, NULL);
 }
 libresolv_hidden_def (res_nsend)
 
 /* Private */
 
+/* The send_vc function is responsible for sending a DNS query over TCP
+   to the nameserver numbered NS from the res_state STATP i.e.
+   EXT(statp).nssocks[ns].  The function supports sending both IPv4 and
+   IPv6 queries at the same serially on the same socket.
+
+   Please note that for TCP there is no way to disable sending both
+   queries, unlike UDP, which honours RES_SNGLKUP and RES_SNGLKUPREOP
+   and sends the queries serially and waits for the result after each
+   sent query.  This implemetnation should be corrected to honour these
+   options.
+
+   Please also note that for TCP we send both queries over the same
+   socket one after another.  This technically violates best practice
+   since the server is allowed to read the first query, respond, and
+   then close the socket (to service another client).  If the server
+   does this, then the remaining second query in the socket data buffer
+   will cause the server to send the client an RST which will arrive
+   asynchronously and the client's OS will likely tear down the socket
+   receive buffer resulting in a potentially short read and lost
+   response data.  This will force the client to retry the query again,
+   and this process may repeat until all servers and connection resets
+   are exhausted and then the query will fail.  It's not known if this
+   happens with any frequency in real DNS server implementations.  This
+   implementation should be corrected to use two sockets by default for
+   parallel queries.
+
+   The query stored in BUF of BUFLEN length is sent first followed by
+   the query stored in BUF2 of BUFLEN2 length.  Queries are sent
+   serially on the same socket.
+
+   Answers to the query are stored firstly in *ANSP up to a max of
+   *ANSSIZP bytes.  If more than *ANSSIZP bytes are needed and ANSCP
+   is non-NULL (to indicate that modifying the answer buffer is allowed)
+   then malloc is used to allocate a new response buffer and ANSCP and
+   ANSP will both point to the new buffer.  If more than *ANSSIZP bytes
+   are needed but ANSCP is NULL, then as much of the response as
+   possible is read into the buffer, but the results will be truncated.
+   When truncation happens because of a small answer buffer the DNS
+   packets header feild TC will bet set to 1, indicating a truncated
+   message and the rest of the socket data will be read and discarded.
+
+   Answers to the query are stored secondly in *ANSP2 up to a max of
+   *ANSSIZP2 bytes, with the actual response length stored in
+   *RESPLEN2.  If more than *ANSSIZP bytes are needed and ANSP2
+   is non-NULL (required for a second query) then malloc is used to
+   allocate a new response buffer, *ANSSIZP2 is set to the new buffer
+   size and *ANSP2_MALLOCED is set to 1.
+
+   The ANSP2_MALLOCED argument will eventually be removed as the
+   change in buffer pointer can be used to detect the buffer has
+   changed and that the caller should use free on the new buffer.
+
+   Note that the answers may arrive in any order from the server and
+   therefore the first and second answer buffers may not correspond to
+   the first and second queries.
+
+   It is not supported to call this function with a non-NULL ANSP2
+   but a NULL ANSCP.  Put another way, you can call send_vc with a
+   single unmodifiable buffer or two modifiable buffers, but no other
+   combination is supported.
+
+   It is the caller's responsibility to free the malloc allocated
+   buffers by detecting that the pointers have changed from their
+   original values i.e. *ANSCP or *ANSP2 has changed.
+
+   If errors are encountered then *TERRNO is set to an appropriate
+   errno value and a zero result is returned for a recoverable error,
+   and a less-than zero result is returned for a non-recoverable error.
+
+   If no errors are encountered then *TERRNO is left unmodified and
+   a the length of the first response in bytes is returned.  */
 static int
 send_vc(res_state statp,
        const u_char *buf, int buflen, const u_char *buf2, int buflen2,
        u_char **ansp, int *anssizp,
        int *terrno, int ns, u_char **anscp, u_char **ansp2, int *anssizp2,
-       int *resplen2)
+       int *resplen2, int *ansp2_malloced)
 {
        const HEADER *hp = (HEADER *) buf;
        const HEADER *hp2 = (HEADER *) buf2;
-       u_char *ans = *ansp;
-       int orig_anssizp = *anssizp;
-       // XXX REMOVE
-       // int anssiz = *anssizp;
-       HEADER *anhp = (HEADER *) ans;
+       HEADER *anhp = (HEADER *) *ansp;
        struct sockaddr_in6 *nsap = EXT(statp).nsaddrs[ns];
        int truncating, connreset, resplen, n;
        struct iovec iov[4];
@@ -741,6 +828,8 @@ send_vc(res_state statp,
         * Receive length & response
         */
        int recvresp1 = 0;
+       /* Skip the second response if there is no second query.
+           To do that we mark the second response as received.  */
        int recvresp2 = buf2 == NULL;
        uint16_t rlen16;
  read_len:
@@ -777,33 +866,14 @@ send_vc(res_state statp,
        u_char **thisansp;
        int *thisresplenp;
        if ((recvresp1 | recvresp2) == 0 || buf2 == NULL) {
+               /* We have not received any responses
+                  yet or we only have one response to
+                  receive.  */
                thisanssizp = anssizp;
                thisansp = anscp ?: ansp;
                assert (anscp != NULL || ansp2 == NULL);
                thisresplenp = &resplen;
        } else {
-               if (*anssizp != MAXPACKET) {
-                       /* No buffer allocated for the first
-                          reply.  We can try to use the rest
-                          of the user-provided buffer.  */
-#ifdef _STRING_ARCH_unaligned
-                       *anssizp2 = orig_anssizp - resplen;
-                       *ansp2 = *ansp + resplen;
-#else
-                       int aligned_resplen
-                         = ((resplen + __alignof__ (HEADER) - 1)
-                            & ~(__alignof__ (HEADER) - 1));
-                       *anssizp2 = orig_anssizp - aligned_resplen;
-                       *ansp2 = *ansp + aligned_resplen;
-#endif
-               } else {
-                       /* The first reply did not fit into the
-                          user-provided buffer.  Maybe the second
-                          answer will.  */
-                       *anssizp2 = orig_anssizp;
-                       *ansp2 = *ansp;
-               }
-
                thisanssizp = anssizp2;
                thisansp = ansp2;
                thisresplenp = resplen2;
@@ -811,10 +881,14 @@ send_vc(res_state statp,
        anhp = (HEADER *) *thisansp;
 
        *thisresplenp = rlen;
-       if (rlen > *thisanssizp) {
-               /* Yes, we test ANSCP here.  If we have two buffers
-                  both will be allocatable.  */
-               if (__builtin_expect (anscp != NULL, 1)) {
+       /* Is the answer buffer too small?  */
+       if (*thisanssizp < rlen) {
+               /* If the current buffer is non-NULL and it's not
+                  pointing at the static user-supplied buffer then
+                  we can reallocate it.  */
+               if (thisansp != NULL && thisansp != ansp) {
+                       /* Always allocate MAXPACKET, callers expect
+                          this specific size.  */
                        u_char *newp = malloc (MAXPACKET);
                        if (newp == NULL) {
                                *terrno = ENOMEM;
@@ -823,7 +897,12 @@ send_vc(res_state statp,
                        }
                        *thisanssizp = MAXPACKET;
                        *thisansp = newp;
+                       if (thisansp == ansp2)
+                         *ansp2_malloced = 1;
                        anhp = (HEADER *) newp;
+                       /* A uint16_t can't be larger than MAXPACKET
+                          thus it's safe to allocate MAXPACKET but
+                          read RLEN bytes instead.  */
                        len = rlen;
                } else {
                        Dprint(statp->options & RES_DEBUG,
@@ -987,17 +1066,75 @@ reopen (res_state statp, int *terrno, int ns)
        return 1;
 }
 
+/* The send_dg function is responsible for sending a DNS query over UDP
+   to the nameserver numbered NS from the res_state STATP i.e.
+   EXT(statp).nssocks[ns].  The function supports IPv4 and IPv6 queries
+   along with the ability to send the query in parallel for both stacks
+   (default) or serially (RES_SINGLKUP).  It also supports serial lookup
+   with a close and reopen of the socket used to talk to the server
+   (RES_SNGLKUPREOP) to work around broken name servers.
+
+   The query stored in BUF of BUFLEN length is sent first followed by
+   the query stored in BUF2 of BUFLEN2 length.  Queries are sent
+   in parallel (default) or serially (RES_SINGLKUP or RES_SNGLKUPREOP).
+
+   Answers to the query are stored firstly in *ANSP up to a max of
+   *ANSSIZP bytes.  If more than *ANSSIZP bytes are needed and ANSCP
+   is non-NULL (to indicate that modifying the answer buffer is allowed)
+   then malloc is used to allocate a new response buffer and ANSCP and
+   ANSP will both point to the new buffer.  If more than *ANSSIZP bytes
+   are needed but ANSCP is NULL, then as much of the response as
+   possible is read into the buffer, but the results will be truncated.
+   When truncation happens because of a small answer buffer the DNS
+   packets header feild TC will bet set to 1, indicating a truncated
+   message, while the rest of the UDP packet is discarded.
+
+   Answers to the query are stored secondly in *ANSP2 up to a max of
+   *ANSSIZP2 bytes, with the actual response length stored in
+   *RESPLEN2.  If more than *ANSSIZP bytes are needed and ANSP2
+   is non-NULL (required for a second query) then malloc is used to
+   allocate a new response buffer, *ANSSIZP2 is set to the new buffer
+   size and *ANSP2_MALLOCED is set to 1.
+
+   The ANSP2_MALLOCED argument will eventually be removed as the
+   change in buffer pointer can be used to detect the buffer has
+   changed and that the caller should use free on the new buffer.
+
+   Note that the answers may arrive in any order from the server and
+   therefore the first and second answer buffers may not correspond to
+   the first and second queries.
+
+   It is not supported to call this function with a non-NULL ANSP2
+   but a NULL ANSCP.  Put another way, you can call send_vc with a
+   single unmodifiable buffer or two modifiable buffers, but no other
+   combination is supported.
+
+   It is the caller's responsibility to free the malloc allocated
+   buffers by detecting that the pointers have changed from their
+   original values i.e. *ANSCP or *ANSP2 has changed.
+
+   If an answer is truncated because of UDP datagram DNS limits then
+   *V_CIRCUIT is set to 1 and the return value non-zero to indicate to
+   the caller to retry with TCP.  The value *GOTSOMEWHERE is set to 1
+   if any progress was made reading a response from the nameserver and
+   is used by the caller to distinguish between ECONNREFUSED and
+   ETIMEDOUT (the latter if *GOTSOMEWHERE is 1).
+
+   If errors are encountered then *TERRNO is set to an appropriate
+   errno value and a zero result is returned for a recoverable error,
+   and a less-than zero result is returned for a non-recoverable error.
+
+   If no errors are encountered then *TERRNO is left unmodified and
+   a the length of the first response in bytes is returned.  */
 static int
 send_dg(res_state statp,
        const u_char *buf, int buflen, const u_char *buf2, int buflen2,
        u_char **ansp, int *anssizp,
        int *terrno, int ns, int *v_circuit, int *gotsomewhere, u_char **anscp,
-       u_char **ansp2, int *anssizp2, int *resplen2)
+       u_char **ansp2, int *anssizp2, int *resplen2, int *ansp2_malloced)
 {
        const HEADER *hp = (HEADER *) buf;
        const HEADER *hp2 = (HEADER *) buf2;
-       u_char *ans = *ansp;
-       int orig_anssizp = *anssizp;
        struct timespec now, timeout, finish;
        struct pollfd pfd[1];
        int ptimeout;
@@ -1029,6 +1166,8 @@ send_dg(res_state statp,
        int need_recompute = 0;
        int nwritten = 0;
        int recvresp1 = 0;
+       /* Skip the second response if there is no second query.
+           To do that we mark the second response as received.  */
        int recvresp2 = buf2 == NULL;
        pfd[0].fd = EXT(statp).nssocks[ns];
        pfd[0].events = POLLOUT;
@@ -1125,50 +1264,53 @@ send_dg(res_state statp,
                int *thisresplenp;
 
                if ((recvresp1 | recvresp2) == 0 || buf2 == NULL) {
+                       /* We have not received any responses
+                          yet or we only have one response to
+                          receive.  */
                        thisanssizp = anssizp;
                        thisansp = anscp ?: ansp;
                        assert (anscp != NULL || ansp2 == NULL);
                        thisresplenp = &resplen;
                } else {
-                       if (*anssizp != MAXPACKET) {
-                               /* No buffer allocated for the first
-                                  reply.  We can try to use the rest
-                                  of the user-provided buffer.  */
-#ifdef _STRING_ARCH_unaligned
-                               *anssizp2 = orig_anssizp - resplen;
-                               *ansp2 = *ansp + resplen;
-#else
-                               int aligned_resplen
-                                 = ((resplen + __alignof__ (HEADER) - 1)
-                                    & ~(__alignof__ (HEADER) - 1));
-                               *anssizp2 = orig_anssizp - aligned_resplen;
-                               *ansp2 = *ansp + aligned_resplen;
-#endif
-                       } else {
-                               /* The first reply did not fit into the
-                                  user-provided buffer.  Maybe the second
-                                  answer will.  */
-                               *anssizp2 = orig_anssizp;
-                               *ansp2 = *ansp;
-                       }
-
                        thisanssizp = anssizp2;
                        thisansp = ansp2;
                        thisresplenp = resplen2;
                }
 
                if (*thisanssizp < MAXPACKET
-                   /* Yes, we test ANSCP here.  If we have two buffers
-                      both will be allocatable.  */
-                   && anscp
+                   /* If the current buffer is non-NULL and it's not
+                      pointing at the static user-supplied buffer then
+                      we can reallocate it.  */
+                   && (thisansp != NULL && thisansp != ansp)
+                   /* Is the size too small?  */
                    && (ioctl (pfd[0].fd, FIONREAD, thisresplenp) < 0
                        || *thisanssizp < *thisresplenp)) {
+                       /* Always allocate MAXPACKET, callers expect
+                          this specific size.  */
                        u_char *newp = malloc (MAXPACKET);
                        if (newp != NULL) {
-                               *anssizp = MAXPACKET;
-                               *thisansp = ans = newp;
+                               *thisanssizp = MAXPACKET;
+                               *thisansp = newp;
+                               if (thisansp == ansp2)
+                                 *ansp2_malloced = 1;
                        }
                }
+               /* We could end up with truncation if anscp was NULL
+                  (not allowed to change caller's buffer) and the
+                  response buffer size is too small.  This isn't a
+                  reliable way to detect truncation because the ioctl
+                  may be an inaccurate report of the UDP message size.
+                  Therefore we use this only to issue debug output.
+                  To do truncation accurately with UDP we need
+                  MSG_TRUNC which is only available on Linux.  We
+                  can abstract out the Linux-specific feature in the
+                  future to detect truncation.  */
+               if (__builtin_expect (*thisanssizp < *thisresplenp, 0)) {
+                       Dprint(statp->options & RES_DEBUG,
+                              (stdout, ";; response may be truncated (UDP)\n")
+                       );
+               }
+
                HEADER *anhp = (HEADER *) *thisansp;
                socklen_t fromlen = sizeof(struct sockaddr_in6);
                assert (sizeof(from) <= fromlen);