about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2018-01-04 12:32:36 +0100
committerFlorian Weimer <fweimer@redhat.com>2018-01-04 12:32:36 +0100
commit0e64ee798605a042a07604e8a4bf0ec00381e28b (patch)
treea7f3a2e21ebd17596bbeed566ab0e231f3aaccee
parente3ae300f3f2d1a94709b0f3fed2543b9449a09ca (diff)
downloadglibc-0e64ee798605a042a07604e8a4bf0ec00381e28b.tar.gz
glibc-0e64ee798605a042a07604e8a4bf0ec00381e28b.tar.xz
glibc-0e64ee798605a042a07604e8a4bf0ec00381e28b.zip
getaddrinfo: Fix error handling in gethosts [BZ #21915] [BZ #21922]
The old code uses errno as the primary indicator for success or
failure.  This is wrong because errno is only set for specific
combinations of the status return value and the h_errno variable.

(cherry picked from commit f4a6be2582b8dfe8adfa68da3dd8decf566b3983)
-rw-r--r--ChangeLog14
-rw-r--r--NEWS1
-rw-r--r--nss/Makefile7
-rw-r--r--nss/tst-nss-files-hosts-erange.c109
-rw-r--r--resolv/tst-resolv-basic.c157
-rw-r--r--sysdeps/posix/getaddrinfo.c39
6 files changed, 269 insertions, 58 deletions
diff --git a/ChangeLog b/ChangeLog
index 519e56e8b3..96a58d5f62 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,19 @@
 2017-09-01  Florian Weimer  <fweimer@redhat.com>
 
+	[BZ #21915]
+	[BZ #21922]
+	* sysdeps/posix/getaddrinfo.c (gethosts): Look at NSS function
+	result to determine success or failure, not the errno value.
+	* nss/Makefile (tests): Add tst-nss-files-hosts-erange.
+	(tst-nss-files-hosts-erange): Link with -ldl.
+	* nss/tst-nss-files-hosts-erange.c: New file.
+	* nss/tst-resolv-basic.c (response): Handle nodata.example.
+	(do_test): Add NO_DATA tests.
+	* resolv/tst-resolv-basic.c (test_nodata_nxdomain): New function.
+	(do_test): Call it.
+
+2017-09-01  Florian Weimer  <fweimer@redhat.com>
+
 	[BZ #21922]
 	* sysdeps/posix/getaddrinfo.c (gaih_inet): Report EAI_NODATA error
 	coming from gethostbyname2_r.
diff --git a/NEWS b/NEWS
index d2636b75bd..1537957839 100644
--- a/NEWS
+++ b/NEWS
@@ -57,6 +57,7 @@ The following bugs are resolved with this release:
   [21624] Unsafe alloca allows local attackers to alias stack and heap (CVE-2017-1000366)
   [21654] nss: Fix invalid cast in group merging
   [21778] Robust mutex may deadlock
+  [21915] getaddrinfo: incorrect result handling for NSS service modules
   [21922] getaddrinfo with AF_INET(6) returns EAI_NONAME, not EAI_NODATA
   [21972] assert macro requires operator== (int) for its argument type
   [22322] libc: [mips64] wrong bits/long-double.h installed
diff --git a/nss/Makefile b/nss/Makefile
index de6c47a1db..def3860152 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -54,6 +54,11 @@ tests			= test-netdb tst-nss-test1 test-digits-dots \
 			  $(tests-static)
 xtests			= bug-erange
 
+# Tests which need libdl
+ifeq (yes,$(build-shared))
+tests += tst-nss-files-hosts-erange
+endif
+
 # If we have a thread library then we can test cancellation against
 # some routines like getpwuid_r.
 ifeq (yes,$(have-thread-library))
@@ -135,3 +140,5 @@ $(objpfx)tst-nss-test1.out: $(objpfx)/libnss_test1.so$(libnss_test1.so-version)
 ifeq (yes,$(have-thread-library))
 $(objpfx)tst-cancel-getpwuid_r: $(shared-thread-library)
 endif
+
+$(objpfx)tst-nss-files-hosts-erange: $(libdl)
diff --git a/nss/tst-nss-files-hosts-erange.c b/nss/tst-nss-files-hosts-erange.c
new file mode 100644
index 0000000000..beb7aa9fa0
--- /dev/null
+++ b/nss/tst-nss-files-hosts-erange.c
@@ -0,0 +1,109 @@
+/* Parse /etc/hosts in multi mode with a trailing long line (bug 21915).
+   Copyright (C) 2017 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/>.  */
+
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <gnu/lib-names.h>
+#include <netdb.h>
+#include <nss.h>
+#include <support/check.h>
+#include <support/check_nss.h>
+#include <support/namespace.h>
+#include <support/test-driver.h>
+#include <support/xunistd.h>
+
+struct support_chroot *chroot_env;
+
+#define X10 "XXXXXXXXXX"
+#define X100 X10 X10 X10 X10 X10 X10 X10 X10 X10 X10
+#define X1000 X100 X100 X100 X100 X100 X100 X100 X100 X100 X100
+
+static void
+prepare (int argc, char **argv)
+{
+  chroot_env = support_chroot_create
+    ((struct support_chroot_configuration)
+     {
+       .resolv_conf = "",
+       .hosts =
+         "127.0.0.1   localhost localhost.localdomain\n"
+         "::1         localhost localhost.localdomain\n"
+         "192.0.2.1   example.com\n"
+         "#" X1000 X100 "\n",
+       .host_conf = "multi on\n",
+     });
+}
+
+static int
+do_test (void)
+{
+  support_become_root ();
+  if (!support_can_chroot ())
+    return EXIT_UNSUPPORTED;
+
+  __nss_configure_lookup ("hosts", "files");
+  if (dlopen (LIBNSS_FILES_SO, RTLD_LAZY) == NULL)
+    FAIL_EXIT1 ("could not load " LIBNSS_DNS_SO ": %s", dlerror ());
+
+  xchroot (chroot_env->path_chroot);
+
+  errno = ERANGE;
+  h_errno = NETDB_INTERNAL;
+  check_hostent ("gethostbyname example.com",
+                 gethostbyname ("example.com"),
+                 "name: example.com\n"
+                 "address: 192.0.2.1\n");
+  errno = ERANGE;
+  h_errno = NETDB_INTERNAL;
+  check_hostent ("gethostbyname2 AF_INET example.com",
+                 gethostbyname2 ("example.com", AF_INET),
+                 "name: example.com\n"
+                 "address: 192.0.2.1\n");
+  {
+    struct addrinfo hints =
+      {
+        .ai_family = AF_UNSPEC,
+        .ai_socktype = SOCK_STREAM,
+        .ai_protocol = IPPROTO_TCP,
+      };
+    errno = ERANGE;
+    h_errno = NETDB_INTERNAL;
+    struct addrinfo *ai;
+    int ret = getaddrinfo ("example.com", "80", &hints, &ai);
+    check_addrinfo ("example.com AF_UNSPEC", ai, ret,
+                    "address: STREAM/TCP 192.0.2.1 80\n");
+    if (ret == 0)
+      freeaddrinfo (ai);
+
+    hints.ai_family = AF_INET;
+    errno = ERANGE;
+    h_errno = NETDB_INTERNAL;
+    ret = getaddrinfo ("example.com", "80", &hints, &ai);
+    check_addrinfo ("example.com AF_INET", ai, ret,
+                    "address: STREAM/TCP 192.0.2.1 80\n");
+    if (ret == 0)
+      freeaddrinfo (ai);
+  }
+
+  support_chroot_free (chroot_env);
+  return 0;
+}
+
+#define PREPARE prepare
+#include <support/test-driver.c>
diff --git a/resolv/tst-resolv-basic.c b/resolv/tst-resolv-basic.c
index 3dfa1657f5..66a0e8a165 100644
--- a/resolv/tst-resolv-basic.c
+++ b/resolv/tst-resolv-basic.c
@@ -22,6 +22,7 @@
 #include <string.h>
 #include <support/check.h>
 #include <support/check_nss.h>
+#include <support/format_nss.h>
 #include <support/resolv_test.h>
 #include <support/support.h>
 
@@ -49,7 +50,7 @@ response (const struct resolv_response_context *ctx,
     qname_compare = qname + 2;
   else
     qname_compare = qname;
-  enum {www, alias, nxdomain, long_name} requested_qname;
+  enum {www, alias, nxdomain, long_name, nodata} requested_qname;
   if (strcmp (qname_compare, "www.example") == 0)
     requested_qname = www;
   else if (strcmp (qname_compare, "alias.example") == 0)
@@ -58,6 +59,8 @@ response (const struct resolv_response_context *ctx,
     requested_qname = nxdomain;
   else if (strcmp (qname_compare, LONG_NAME) == 0)
     requested_qname = long_name;
+  else if (strcmp (qname_compare, "nodata.example") == 0)
+    requested_qname = nodata;
   else
     {
       support_record_failure ();
@@ -86,6 +89,8 @@ response (const struct resolv_response_context *ctx,
       resolv_response_close_record (b);
       resolv_response_open_record (b, "www.example", qclass, qtype, 0);
       break;
+    case nodata:
+      return;
     case nxdomain:
       FAIL_EXIT1 ("unreachable");
     }
@@ -204,6 +209,117 @@ check_ai (const char *name, const char *service,
                          expected);
 }
 
+/* Test for bug 21295: getaddrinfo used to discard address information
+   instead of merging it.  */
+static void
+test_bug_21295 (void)
+{
+  /* The address order is unpredictable.  There are two factors which
+     contribute to that: The stub resolver does not perform proper
+     response matching for A/AAAA queries (an A response could be
+     associated with an AAAA query and vice versa), and without
+     namespaces, system configuration could affect address
+     ordering.  */
+  for (int do_tcp = 0; do_tcp < 2; ++do_tcp)
+    {
+      const struct addrinfo hints =
+        {
+          .ai_family = AF_INET6,
+          .ai_socktype = SOCK_STREAM,
+          .ai_flags = AI_V4MAPPED | AI_ALL,
+        };
+      const char *qname;
+      if (do_tcp)
+        qname = "t.www.example";
+      else
+        qname = "www.example";
+      struct addrinfo *ai = NULL;
+      int ret = getaddrinfo (qname, "80", &hints, &ai);
+      TEST_VERIFY_EXIT (ret == 0);
+
+      const char *expected_a;
+      const char *expected_b;
+      if (do_tcp)
+        {
+          expected_a = "flags: AI_V4MAPPED AI_ALL\n"
+            "address: STREAM/TCP 2001:db8::3 80\n"
+            "address: STREAM/TCP ::ffff:192.0.2.19 80\n";
+          expected_b = "flags: AI_V4MAPPED AI_ALL\n"
+            "address: STREAM/TCP ::ffff:192.0.2.19 80\n"
+            "address: STREAM/TCP 2001:db8::3 80\n";
+        }
+      else
+        {
+          expected_a = "flags: AI_V4MAPPED AI_ALL\n"
+            "address: STREAM/TCP 2001:db8::1 80\n"
+            "address: STREAM/TCP ::ffff:192.0.2.17 80\n";
+          expected_b = "flags: AI_V4MAPPED AI_ALL\n"
+            "address: STREAM/TCP ::ffff:192.0.2.17 80\n"
+            "address: STREAM/TCP 2001:db8::1 80\n";
+        }
+
+      char *actual = support_format_addrinfo (ai, ret);
+      if (!(strcmp (actual, expected_a) == 0
+            || strcmp (actual, expected_b) == 0))
+        {
+          support_record_failure ();
+          printf ("error: %s: unexpected response (TCP: %d):\n%s\n",
+                  __func__, do_tcp, actual);
+        }
+      free (actual);
+      freeaddrinfo (ai);
+    }
+}
+
+/* Run tests which do not expect any data.  */
+static void
+test_nodata_nxdomain (void)
+{
+  /* Iterate through different address families.  */
+  int families[] = { AF_UNSPEC, AF_INET, AF_INET6, -1 };
+  for (int i = 0; families[i] >= 0; ++i)
+    /* If do_tcp, prepend "t." to the name to trigger TCP
+       fallback.  */
+    for (int do_tcp = 0; do_tcp < 2; ++do_tcp)
+      /* If do_nxdomain, trigger an NXDOMAIN error (DNS failure),
+         otherwise use a NODATA response (empty but successful
+         answer).  */
+      for (int do_nxdomain = 0; do_nxdomain < 2; ++do_nxdomain)
+        {
+          int family = families[i];
+          char *name = xasprintf ("%s%s.example",
+                                  do_tcp ? "t." : "",
+                                  do_nxdomain ? "nxdomain" : "nodata");
+
+          if (family != AF_UNSPEC)
+            {
+              if (do_nxdomain)
+                check_h (name, family, "error: HOST_NOT_FOUND\n");
+              else
+                check_h (name, family, "error: NO_ADDRESS\n");
+            }
+
+          const char *expected;
+          if (do_nxdomain)
+            expected = "error: Name or service not known\n";
+          else
+            expected = "error: No address associated with hostname\n";
+
+          check_ai (name, "80", family, expected);
+
+          struct addrinfo hints =
+            {
+              .ai_family = family,
+              .ai_flags = AI_V4MAPPED | AI_ALL,
+            };
+          check_ai_hints (name, "80", hints, expected);
+          hints.ai_flags |= AI_CANONNAME;
+          check_ai_hints (name, "80", hints, expected);
+
+          free (name);
+        }
+}
+
 static int
 do_test (void)
 {
@@ -376,43 +492,8 @@ do_test (void)
             "address: DGRAM/UDP 2001:db8::4 80\n"
             "address: RAW/IP 2001:db8::4 80\n");
 
-  check_h ("nxdomain.example", AF_INET,
-           "error: HOST_NOT_FOUND\n");
-  check_h ("nxdomain.example", AF_INET6,
-           "error: HOST_NOT_FOUND\n");
-  check_ai ("nxdomain.example", "80", AF_UNSPEC,
-            "error: Name or service not known\n");
-  check_ai ("nxdomain.example", "80", AF_INET,
-            "error: Name or service not known\n");
-  check_ai ("nxdomain.example", "80", AF_INET6,
-            "error: Name or service not known\n");
-
-  check_h ("t.nxdomain.example", AF_INET,
-           "error: HOST_NOT_FOUND\n");
-  check_h ("t.nxdomain.example", AF_INET6,
-           "error: HOST_NOT_FOUND\n");
-  check_ai ("t.nxdomain.example", "80", AF_UNSPEC,
-            "error: Name or service not known\n");
-  check_ai ("t.nxdomain.example", "80", AF_INET,
-            "error: Name or service not known\n");
-  check_ai ("t.nxdomain.example", "80", AF_INET6,
-            "error: Name or service not known\n");
-
-  /* Test for bug 21295.  */
-  check_ai_hints ("www.example", "80",
-                  (struct addrinfo) { .ai_family = AF_INET6,
-                      .ai_socktype = SOCK_STREAM,
-                      .ai_flags = AI_V4MAPPED | AI_ALL, },
-                  "flags: AI_V4MAPPED AI_ALL\n"
-                  "address: STREAM/TCP 2001:db8::1 80\n"
-                  "address: STREAM/TCP ::ffff:192.0.2.17 80\n");
-  check_ai_hints ("t.www.example", "80",
-                  (struct addrinfo) { .ai_family = AF_INET6,
-                      .ai_socktype = SOCK_STREAM,
-                      .ai_flags = AI_V4MAPPED | AI_ALL, },
-                  "flags: AI_V4MAPPED AI_ALL\n"
-                  "address: STREAM/TCP 2001:db8::3 80\n"
-                  "address: STREAM/TCP ::ffff:192.0.2.19 80\n");
+  test_bug_21295 ();
+  test_nodata_nxdomain ();
 
   resolv_test_end (aux);
 
diff --git a/sysdeps/posix/getaddrinfo.c b/sysdeps/posix/getaddrinfo.c
index bcd437c022..43cebb551a 100644
--- a/sysdeps/posix/getaddrinfo.c
+++ b/sysdeps/posix/getaddrinfo.c
@@ -241,26 +241,25 @@ convert_hostent_to_gaih_addrtuple (const struct addrinfo *req,
 #define gethosts(_family, _type) \
  {									      \
   struct hostent th;							      \
-  struct hostent *h;							      \
   char *localcanon = NULL;						      \
   no_data = 0;								      \
-  while (1) {								      \
-    status = DL_CALL_FCT (fct, (name, _family, &th,			      \
-				tmpbuf->data, tmpbuf->length,		      \
-				&errno, &h_errno, NULL, &localcanon));	      \
-    if (errno != ERANGE || h_errno != NETDB_INTERNAL)			      \
-      break;								      \
-    if (!scratch_buffer_grow (tmpbuf))					      \
-      {									      \
-	result = -EAI_MEMORY;						      \
-	goto free_and_return;						      \
-      }									      \
-  }									      \
-  if (status == NSS_STATUS_SUCCESS && errno == 0)			      \
-    h = &th;								      \
-  else									      \
-    h = NULL;								      \
-  if (errno != 0)							      \
+  while (1)								      \
+    {									      \
+      status = DL_CALL_FCT (fct, (name, _family, &th,			      \
+				  tmpbuf->data, tmpbuf->length,		      \
+				  &errno, &h_errno, NULL, &localcanon));      \
+      if (status != NSS_STATUS_TRYAGAIN || h_errno != NETDB_INTERNAL	      \
+	  || errno != ERANGE)						      \
+	break;								      \
+      if (!scratch_buffer_grow (tmpbuf))				      \
+	{								      \
+	  _res.options |= old_res_options & DEPRECATED_RES_USE_INET6;	      \
+	  result = -EAI_MEMORY;						      \
+	  goto free_and_return;						      \
+	}								      \
+    }									      \
+  if (status == NSS_STATUS_NOTFOUND					      \
+      || status == NSS_STATUS_TRYAGAIN || status == NSS_STATUS_UNAVAIL)	      \
     {									      \
       if (h_errno == NETDB_INTERNAL)					      \
 	{								      \
@@ -273,9 +272,9 @@ convert_hostent_to_gaih_addrtuple (const struct addrinfo *req,
       else								      \
 	no_data = h_errno == NO_DATA;					      \
     }									      \
-  else if (h != NULL)							      \
+  else if (status == NSS_STATUS_SUCCESS)				      \
     {									      \
-      if (!convert_hostent_to_gaih_addrtuple (req, _family,h, &addrmem))      \
+      if (!convert_hostent_to_gaih_addrtuple (req, _family, &th, &addrmem))   \
 	{								      \
 	  _res.options |= old_res_options & DEPRECATED_RES_USE_INET6;	      \
 	  result = -EAI_SYSTEM;						      \