From 916124ed841745b7a1e0fbc43f9909340b47d373 Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Fri, 6 Jul 2018 14:23:15 +0200 Subject: nss_files: Fix re-reading of long lines [BZ #18991] Use the new __libc_readline_unlocked function to pick up reading at the same line in case the buffer needs to be enlarged. --- nss/tst-nss-files-hosts-getent.c | 276 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 nss/tst-nss-files-hosts-getent.c (limited to 'nss/tst-nss-files-hosts-getent.c') diff --git a/nss/tst-nss-files-hosts-getent.c b/nss/tst-nss-files-hosts-getent.c new file mode 100644 index 0000000000..d7514e8e38 --- /dev/null +++ b/nss/tst-nss-files-hosts-getent.c @@ -0,0 +1,276 @@ +/* Enumerate /etc/hosts with a long line (bug 18991). + 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 + . */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct support_chroot *chroot_env; + +/* Number of alias names in the long line. This is varied to catch + different cases where the ERANGE handling can go wrong (line buffer + length, alias buffer). */ +static int name_count; + +/* Write /etc/hosts, from outside of the chroot. */ +static void +write_hosts (void) +{ + FILE *fp = xfopen (chroot_env->path_hosts, "w"); + fputs ("127.0.0.1 localhost localhost.localdomain\n", fp); + fputs ("192.0.2.2 host2.example.com\n", fp); + fputs ("192.0.2.1", fp); + for (int i = 0; i < name_count; ++i) + fprintf (fp, " host%d.example.com", i); + fputs ("\n192.0.2.80 www.example.com\n" + "192.0.2.5 host5.example.com\n" + "192.0.2.81 www1.example.com\n", fp); + xfclose (fp); +} + +const char *host1_expected = + "name: localhost\n" + "alias: localhost.localdomain\n" + "address: 127.0.0.1\n"; +const char *host2_expected = + "name: host2.example.com\n" + "address: 192.0.2.2\n"; +const char *host4_expected = + "name: www.example.com\n" + "address: 192.0.2.80\n"; +const char *host5_expected = + "name: host5.example.com\n" + "address: 192.0.2.5\n"; +const char *host6_expected = + "name: www1.example.com\n" + "address: 192.0.2.81\n"; + +static void +prepare (int argc, char **argv) +{ + chroot_env = support_chroot_create + ((struct support_chroot_configuration) + { + .resolv_conf = "", + .hosts = "", /* Filled in by write_hosts. */ + .host_conf = "multi on\n", + }); +} + +/* If -1, no sethostent call. Otherwise, pass do_stayopen as the + sethostent argument. */ +static int do_stayopen; + +/* If non-zero, perform an endostent call. */ +static int do_endent; + +static void +subprocess_getent (void *closure) +{ + xchroot (chroot_env->path_chroot); + + errno = 0; + if (do_stayopen >= 0) + sethostent (do_stayopen); + TEST_VERIFY (errno == 0); + + int i = 0; + while (true) + { + struct xmemstream expected; + xopen_memstream (&expected); + switch (++i) + { + case 1: + fputs (host1_expected, expected.out); + break; + case 2: + fputs (host2_expected, expected.out); + break; + case 3: + fputs ("name: host0.example.com\n", expected.out); + for (int j = 1; j < name_count; ++j) + fprintf (expected.out, "alias: host%d.example.com\n", j); + fputs ("address: 192.0.2.1\n", expected.out); + break; + case 4: + fputs (host4_expected, expected.out); + break; + case 5: + fputs (host5_expected, expected.out); + break; + case 6: + fputs (host6_expected, expected.out); + break; + default: + fprintf (expected.out, "*** unexpected host %d ***\n", i); + break; + } + xfclose_memstream (&expected); + char *context = xasprintf ("do_stayopen=%d host=%d", do_stayopen, i); + + errno = 0; + struct hostent *e = gethostent (); + if (e == NULL) + { + TEST_VERIFY (errno == 0); + break; + } + check_hostent (context, e, expected.buffer); + free (context); + free (expected.buffer); + } + + errno = 0; + if (do_endent) + endhostent (); + TEST_VERIFY (errno == 0); + + /* Exercise process termination. */ + exit (0); +} + +/* getaddrinfo test. To be run from a subprocess. */ +static void +test_gai (int family) +{ + struct addrinfo hints = + { + .ai_family = family, + .ai_protocol = IPPROTO_TCP, + .ai_socktype = SOCK_STREAM, + }; + + struct addrinfo *ai; + int ret = getaddrinfo ("host2.example.com", "80", &hints, &ai); + check_addrinfo ("host2.example.com", ai, ret, + "address: STREAM/TCP 192.0.2.2 80\n" + "address: STREAM/TCP 192.0.2.1 80\n"); + + ret = getaddrinfo ("host5.example.com", "80", &hints, &ai); + check_addrinfo ("host5.example.com", ai, ret, + "address: STREAM/TCP 192.0.2.1 80\n" + "address: STREAM/TCP 192.0.2.5 80\n"); + + ret = getaddrinfo ("www.example.com", "80", &hints, &ai); + check_addrinfo ("www.example.com", ai, ret, + "address: STREAM/TCP 192.0.2.80 80\n"); + + ret = getaddrinfo ("www1.example.com", "80", &hints, &ai); + check_addrinfo ("www1.example.com", ai, ret, + "address: STREAM/TCP 192.0.2.81 80\n"); +} + +/* Subprocess routine for gethostbyname/getaddrinfo testing. */ +static void +subprocess_gethost (void *closure) +{ + xchroot (chroot_env->path_chroot); + + /* This tests enlarging the read buffer in the multi case. */ + struct xmemstream expected; + xopen_memstream (&expected); + fputs ("name: host2.example.com\n", expected.out); + for (int j = 1; j < name_count; ++j) + /* NB: host2 is duplicated in the alias list. */ + fprintf (expected.out, "alias: host%d.example.com\n", j); + fputs ("alias: host0.example.com\n" + "address: 192.0.2.2\n" + "address: 192.0.2.1\n", + expected.out); + xfclose_memstream (&expected); + check_hostent ("host2.example.com", + gethostbyname ("host2.example.com"), + expected.buffer); + free (expected.buffer); + + /* Similarly, but with a different order in the /etc/hosts file. */ + xopen_memstream (&expected); + fputs ("name: host0.example.com\n", expected.out); + for (int j = 1; j < name_count; ++j) + fprintf (expected.out, "alias: host%d.example.com\n", j); + /* NB: host5 is duplicated in the alias list. */ + fputs ("alias: host5.example.com\n" + "address: 192.0.2.1\n" + "address: 192.0.2.5\n", + expected.out); + xfclose_memstream (&expected); + check_hostent ("host5.example.com", + gethostbyname ("host5.example.com"), + expected.buffer); + free (expected.buffer); + + check_hostent ("www.example.com", + gethostbyname ("www.example.com"), + host4_expected); + check_hostent ("www1.example.com", + gethostbyname ("www1.example.com"), + host6_expected); + + test_gai (AF_INET); + test_gai (AF_UNSPEC); +} + +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 ()); + + /* Each name takes about 20 bytes, so this covers a wide range of + buffer sizes, from less than 1000 bytes to about 18000 bytes. */ + for (name_count = 40; name_count <= 850; ++name_count) + { + write_hosts (); + + for (do_stayopen = -1; do_stayopen < 2; ++do_stayopen) + for (do_endent = 0; do_endent < 2; ++do_endent) + { + if (test_verbose > 0) + printf ("info: name_count=%d do_stayopen=%d do_endent=%d\n", + name_count, do_stayopen, do_endent); + support_isolate_in_subprocess (subprocess_getent, NULL); + } + + support_isolate_in_subprocess (subprocess_gethost, NULL); + } + + support_chroot_free (chroot_env); + return 0; +} + +#define PREPARE prepare +#include -- cgit 1.4.1