]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
getaddrinfo: Fix error handling in gethosts [BZ #21915] [BZ #21922]
authorFlorian Weimer <fweimer@redhat.com>
Thu, 4 Jan 2018 11:32:36 +0000 (12:32 +0100)
committerFlorian Weimer <fweimer@redhat.com>
Thu, 4 Jan 2018 11:32:36 +0000 (12:32 +0100)
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)

ChangeLog
NEWS
nss/Makefile
nss/tst-nss-files-hosts-erange.c [new file with mode: 0644]
resolv/tst-resolv-basic.c
sysdeps/posix/getaddrinfo.c

index 519e56e8b31a2ae2bcc122f514bd47490092070d..96a58d5f624bdba3698d7488d6dbffa871ee28f8 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+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]
diff --git a/NEWS b/NEWS
index d2636b75bdabe8de4f094b1c743418910ce36eda..15379578399043eb210292ac608bcc282ac7f34b 100644 (file)
--- 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
index de6c47a1dbab9879ddf2d23cad352e129fc9ee18..def3860152609a2f3bbd18ccb4292d18532661a4 100644 (file)
@@ -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 (file)
index 0000000..beb7aa9
--- /dev/null
@@ -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>
index 3dfa1657f50ab5880cb18a57d9e33a681efbc5c2..66a0e8a1659219b491fc486a0a856d41b78cb687 100644 (file)
@@ -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);
 
index bcd437c0223b4a1c9f655faed4682168ba47fada..43cebb551a137ea9325bec1e402b9d4ee0c950f3 100644 (file)
@@ -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;                                               \