diff options
author | Florian Weimer <fweimer@redhat.com> | 2017-10-11 07:07:51 +0200 |
---|---|---|
committer | Florian Weimer <fweimer@redhat.com> | 2017-10-11 07:07:51 +0200 |
commit | d8425e116cdd954fea0c04c0f406179b5daebbb3 (patch) | |
tree | 0d82c5f6f571653665912cf0fd0d3062c594695f /nss/tst-nss-files-hosts-multi.c | |
parent | 78e806fd8cd8c918d3bbe1bcdf9091ab365e4a69 (diff) | |
download | glibc-d8425e116cdd954fea0c04c0f406179b5daebbb3.tar.gz glibc-d8425e116cdd954fea0c04c0f406179b5daebbb3.tar.xz glibc-d8425e116cdd954fea0c04c0f406179b5daebbb3.zip |
nss_files: Avoid large buffers with many host addresses [BZ #22078]
The previous implementation had at least a quadratic space requirement in the number of host addresses and aliases.
Diffstat (limited to 'nss/tst-nss-files-hosts-multi.c')
-rw-r--r-- | nss/tst-nss-files-hosts-multi.c | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/nss/tst-nss-files-hosts-multi.c b/nss/tst-nss-files-hosts-multi.c new file mode 100644 index 0000000000..195a19be4f --- /dev/null +++ b/nss/tst-nss-files-hosts-multi.c @@ -0,0 +1,331 @@ +/* Parse /etc/hosts in multi mode with many addresses/aliases. + 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 <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <support/check.h> +#include <support/check_nss.h> +#include <support/namespace.h> +#include <support/support.h> +#include <support/test-driver.h> +#include <support/test-driver.h> +#include <support/xmemstream.h> +#include <support/xstdio.h> +#include <support/xunistd.h> +#include <sys/resource.h> + +struct support_chroot *chroot_env; + +static void +prepare (int argc, char **argv) +{ + chroot_env = support_chroot_create + ((struct support_chroot_configuration) + { + .resolv_conf = "", + .hosts = "", /* See write_hosts below. */ + .host_conf = "multi on\n", + }); +} + +/* Create the /etc/hosts file from outside the chroot. */ +static void +write_hosts (int count) +{ + TEST_VERIFY (count > 0 && count <= 65535); + FILE *fp = xfopen (chroot_env->path_hosts, "w"); + fputs ("127.0.0.1 localhost localhost.localdomain\n" + "::1 localhost localhost.localdomain\n", + fp); + for (int i = 0; i < count; ++i) + { + fprintf (fp, "10.4.%d.%d www4.example.com\n", + (i / 256) & 0xff, i & 0xff); + fprintf (fp, "10.46.%d.%d www.example.com\n", + (i / 256) & 0xff, i & 0xff); + fprintf (fp, "192.0.2.1 alias.example.com v4-%d.example.com\n", i); + fprintf (fp, "2001:db8::6:%x www6.example.com\n", i); + fprintf (fp, "2001:db8::46:%x www.example.com\n", i); + fprintf (fp, "2001:db8::1 alias.example.com v6-%d.example.com\n", i); + } + xfclose (fp); +} + +/* Parameters of a single test. */ +struct test_params +{ + const char *name; /* Name to query. */ + const char *marker; /* Address marker for the name. */ + int count; /* Number of addresses/aliases. */ + int family; /* AF_INET, AF_INET_6 or AF_UNSPEC. */ + bool canonname; /* True if AI_CANONNAME should be enabled. */ +}; + +/* Expected result of gethostbyname/gethostbyname2. */ +static char * +expected_ghbn (const struct test_params *params) +{ + TEST_VERIFY (params->family == AF_INET || params->family == AF_INET6); + + struct xmemstream expected; + xopen_memstream (&expected); + if (strcmp (params->name, "alias.example.com") == 0) + { + fprintf (expected.out, "name: %s\n", params->name); + char af; + if (params->family == AF_INET) + af = '4'; + else + af = '6'; + for (int i = 0; i < params->count; ++i) + fprintf (expected.out, "alias: v%c-%d.example.com\n", af, i); + + for (int i = 0; i < params->count; ++i) + if (params->family == AF_INET) + fputs ("address: 192.0.2.1\n", expected.out); + else + fputs ("address: 2001:db8::1\n", expected.out); + } + else /* www/www4/www6 name. */ + { + bool do_ipv4 = params->family == AF_INET + && strncmp (params->name, "www6", 4) != 0; + bool do_ipv6 = params->family == AF_INET6 + && strncmp (params->name, "www4", 4) != 0; + if (do_ipv4 || do_ipv6) + { + fprintf (expected.out, "name: %s\n", params->name); + if (do_ipv4) + for (int i = 0; i < params->count; ++i) + fprintf (expected.out, "address: 10.%s.%d.%d\n", + params->marker, i / 256, i % 256); + if (do_ipv6) + for (int i = 0; i < params->count; ++i) + fprintf (expected.out, "address: 2001:db8::%s:%x\n", + params->marker, i); + } + else + fputs ("error: HOST_NOT_FOUND\n", expected.out); + } + xfclose_memstream (&expected); + return expected.buffer; +} + +/* Expected result of getaddrinfo. */ +static char * +expected_gai (const struct test_params *params) +{ + bool do_ipv4 = false; + bool do_ipv6 = false; + if (params->family == AF_UNSPEC) + do_ipv4 = do_ipv6 = true; + else if (params->family == AF_INET) + do_ipv4 = true; + else if (params->family == AF_INET6) + do_ipv6 = true; + + struct xmemstream expected; + xopen_memstream (&expected); + if (strcmp (params->name, "alias.example.com") == 0) + { + if (params->canonname) + fprintf (expected.out, + "flags: AI_CANONNAME\n" + "canonname: %s\n", + params->name); + + if (do_ipv4) + for (int i = 0; i < params->count; ++i) + fputs ("address: STREAM/TCP 192.0.2.1 80\n", expected.out); + if (do_ipv6) + for (int i = 0; i < params->count; ++i) + fputs ("address: STREAM/TCP 2001:db8::1 80\n", expected.out); + } + else /* www/www4/www6 name. */ + { + if (strncmp (params->name, "www4", 4) == 0) + do_ipv6 = false; + else if (strncmp (params->name, "www6", 4) == 0) + do_ipv4 = false; + /* Otherwise, we have www as the name, so we do both. */ + + if (do_ipv4 || do_ipv6) + { + if (params->canonname) + fprintf (expected.out, + "flags: AI_CANONNAME\n" + "canonname: %s\n", + params->name); + + if (do_ipv4) + for (int i = 0; i < params->count; ++i) + fprintf (expected.out, "address: STREAM/TCP 10.%s.%d.%d 80\n", + params->marker, i / 256, i % 256); + if (do_ipv6) + for (int i = 0; i < params->count; ++i) + fprintf (expected.out, + "address: STREAM/TCP 2001:db8::%s:%x 80\n", + params->marker, i); + } + else + fputs ("error: Name or service not known\n", expected.out); + } + xfclose_memstream (&expected); + return expected.buffer; +} + +static void +run_gbhn_gai (struct test_params *params) +{ + char *ctx = xasprintf ("name=%s marker=%s count=%d family=%d", + params->name, params->marker, params->count, + params->family); + if (test_verbose > 0) + printf ("info: %s\n", ctx); + + /* Check gethostbyname, gethostbyname2. */ + if (params->family == AF_INET) + { + char *expected = expected_ghbn (params); + check_hostent (ctx, gethostbyname (params->name), expected); + free (expected); + } + if (params->family != AF_UNSPEC) + { + char *expected = expected_ghbn (params); + check_hostent (ctx, gethostbyname2 (params->name, params->family), + expected); + free (expected); + } + + /* Check getaddrinfo. */ + for (int do_canonical = 0; do_canonical < 2; ++do_canonical) + { + params->canonname = do_canonical; + char *expected = expected_gai (params); + struct addrinfo hints = + { + .ai_family = params->family, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + if (do_canonical) + hints.ai_flags |= AI_CANONNAME; + struct addrinfo *ai; + int ret = getaddrinfo (params->name, "80", &hints, &ai); + check_addrinfo (ctx, ai, ret, expected); + if (ret == 0) + freeaddrinfo (ai); + free (expected); + } + + free (ctx); +} + +/* Callback for the subprocess which runs the test in a chroot. */ +static void +subprocess (void *closure) +{ + struct test_params *params = closure; + + xchroot (chroot_env->path_chroot); + + static const int families[] = { AF_INET, AF_INET6, AF_UNSPEC, -1 }; + static const char *const names[] = + { + "www.example.com", "www4.example.com", "www6.example.com", + "alias.example.com", + NULL + }; + static const char *const names_marker[] = { "46", "4", "6", "" }; + + for (int family_idx = 0; families[family_idx] >= 0; ++family_idx) + { + params->family = families[family_idx]; + for (int names_idx = 0; names[names_idx] != NULL; ++names_idx) + { + params->name = names[names_idx]; + params->marker = names_marker[names_idx]; + run_gbhn_gai (params); + } + } +} + +/* Run the test for a specific number of addresses/aliases. */ +static void +run_test (int count) +{ + write_hosts (count); + + struct test_params params = + { + .count = count, + }; + + support_isolate_in_subprocess (subprocess, ¶ms); +} + +static int +do_test (void) +{ + support_become_root (); + if (!support_can_chroot ()) + return EXIT_UNSUPPORTED; + + /* This test should not use gigabytes of memory. */ + { + struct rlimit limit; + if (getrlimit (RLIMIT_AS, &limit) != 0) + { + printf ("getrlimit (RLIMIT_AS) failed: %m\n"); + return 1; + } + long target = 200 * 1024 * 1024; + if (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur > target) + { + limit.rlim_cur = target; + if (setrlimit (RLIMIT_AS, &limit) != 0) + { + printf ("setrlimit (RLIMIT_AS) failed: %m\n"); + return 1; + } + } + } + + __nss_configure_lookup ("hosts", "files"); + if (dlopen (LIBNSS_FILES_SO, RTLD_LAZY) == NULL) + FAIL_EXIT1 ("could not load " LIBNSS_DNS_SO ": %s", dlerror ()); + + /* Run the tests with a few different address/alias counts. */ + for (int count = 1; count <= 111; ++count) + run_test (count); + run_test (1111); + run_test (22222); + + support_chroot_free (chroot_env); + return 0; +} + +#define PREPARE prepare +#include <support/test-driver.c> |