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.
This commit is contained in:
Florian Weimer 2017-09-01 09:34:29 +02:00
parent 5f8340f583
commit f4a6be2582
5 changed files with 205 additions and 45 deletions

View File

@ -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]

View File

@ -60,6 +60,11 @@ tests = test-netdb test-digits-dots tst-nss-getpwent bug17079 \
tst-nss-test5
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))
@ -156,3 +161,5 @@ $(patsubst %,$(objpfx)%.out,$(tests)) : \
ifeq (yes,$(have-thread-library))
$(objpfx)tst-cancel-getpwuid_r: $(shared-thread-library)
endif
$(objpfx)tst-nss-files-hosts-erange: $(libdl)

View File

@ -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>

View File

@ -50,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)
@ -59,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 ();
@ -87,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");
}
@ -267,6 +271,55 @@ test_bug_21295 (void)
}
}
/* 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)
{
@ -439,29 +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_bug_21295 ();
test_nodata_nxdomain ();
resolv_test_end (aux);

View File

@ -242,28 +242,26 @@ 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)) \
{ \
__resolv_context_enable_inet6 (res_ctx, res_enable_inet6); \
__resolv_context_put (res_ctx); \
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)) \
{ \
__resolv_context_enable_inet6 (res_ctx, res_enable_inet6); \
__resolv_context_put (res_ctx); \
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) \
{ \
@ -277,9 +275,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)) \
{ \
__resolv_context_enable_inet6 (res_ctx, res_enable_inet6); \
__resolv_context_put (res_ctx); \