commit 2f498f3d140ab5152bd784df2be7af7d9c5e63ed Author: Florian Weimer Date: Tue Aug 14 10:57:48 2018 +0200 nss_files: Fix file stream leak in aliases lookup [BZ #23521] In order to get a clean test case, it was necessary to fix partially fixed bug 23522 as well. (cherry picked from commit e95c6f61920a0f9237cfb292fa44ad500e1df09b) diff --git a/nss/Makefile b/nss/Makefile index 66fac7f5b8a4c0d8..5209fc0456dd6786 100644 --- a/nss/Makefile +++ b/nss/Makefile @@ -65,6 +65,7 @@ ifeq (yes,$(build-shared)) tests += tst-nss-files-hosts-erange tests += tst-nss-files-hosts-multi tests += tst-nss-files-hosts-getent +tests += tst-nss-files-alias-leak endif # If we have a thread library then we can test cancellation against @@ -171,3 +172,5 @@ endif $(objpfx)tst-nss-files-hosts-erange: $(libdl) $(objpfx)tst-nss-files-hosts-multi: $(libdl) $(objpfx)tst-nss-files-hosts-getent: $(libdl) +$(objpfx)tst-nss-files-alias-leak: $(libdl) +$(objpfx)tst-nss-files-alias-leak.out: $(objpfx)/libnss_files.so diff --git a/nss/nss_files/files-alias.c b/nss/nss_files/files-alias.c index cfd34b66b921bbff..35b0bfc5d2479ab6 100644 --- a/nss/nss_files/files-alias.c +++ b/nss/nss_files/files-alias.c @@ -221,6 +221,13 @@ get_next_alias (FILE *stream, const char *match, struct aliasent *result, { while (! feof_unlocked (listfile)) { + if (room_left < 2) + { + free (old_line); + fclose (listfile); + goto no_more_room; + } + first_unused[room_left - 1] = '\xff'; line = fgets_unlocked (first_unused, room_left, listfile); @@ -229,6 +236,7 @@ get_next_alias (FILE *stream, const char *match, struct aliasent *result, if (first_unused[room_left - 1] != '\xff') { free (old_line); + fclose (listfile); goto no_more_room; } @@ -256,6 +264,7 @@ get_next_alias (FILE *stream, const char *match, struct aliasent *result, + __alignof__ (char *))) { free (old_line); + fclose (listfile); goto no_more_room; } room_left -= ((first_unused - cp) diff --git a/nss/tst-nss-files-alias-leak.c b/nss/tst-nss-files-alias-leak.c new file mode 100644 index 0000000000000000..26d38e2dba1ddaf3 --- /dev/null +++ b/nss/tst-nss-files-alias-leak.c @@ -0,0 +1,237 @@ +/* Check for file descriptor leak in alias :include: processing (bug 23521). + Copyright (C) 2018 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 + +static struct support_chroot *chroot_env; + +/* Number of the aliases for the "many" user. This must be large + enough to trigger reallocation for the pointer array, but result in + answers below the maximum size tried in do_test. */ +enum { many_aliases = 30 }; + +static void +prepare (int argc, char **argv) +{ + chroot_env = support_chroot_create + ((struct support_chroot_configuration) { } ); + + char *path = xasprintf ("%s/etc/aliases", chroot_env->path_chroot); + add_temp_file (path); + support_write_file_string + (path, + "user1: :include:/etc/aliases.user1\n" + "user2: :include:/etc/aliases.user2\n" + "comment: comment1, :include:/etc/aliases.comment\n" + "many: :include:/etc/aliases.many\n"); + free (path); + + path = xasprintf ("%s/etc/aliases.user1", chroot_env->path_chroot); + add_temp_file (path); + support_write_file_string (path, "alias1\n"); + free (path); + + path = xasprintf ("%s/etc/aliases.user2", chroot_env->path_chroot); + add_temp_file (path); + support_write_file_string (path, "alias1a, alias2\n"); + free (path); + + path = xasprintf ("%s/etc/aliases.comment", chroot_env->path_chroot); + add_temp_file (path); + support_write_file_string + (path, + /* The line must be longer than the line with the :include: + directive in /etc/aliases. */ + "# Long line. ##############################################\n" + "comment2\n"); + free (path); + + path = xasprintf ("%s/etc/aliases.many", chroot_env->path_chroot); + add_temp_file (path); + FILE *fp = xfopen (path, "w"); + for (int i = 0; i < many_aliases; ++i) + fprintf (fp, "a%d\n", i); + TEST_VERIFY_EXIT (! ferror (fp)); + xfclose (fp); + free (path); +} + +/* The names of the users to test. */ +static const char *users[] = { "user1", "user2", "comment", "many" }; + +static void +check_aliases (int id, const struct aliasent *e) +{ + TEST_VERIFY_EXIT (id >= 0 || id < array_length (users)); + const char *name = users[id]; + TEST_COMPARE_BLOB (e->alias_name, strlen (e->alias_name), + name, strlen (name)); + + switch (id) + { + case 0: + TEST_COMPARE (e->alias_members_len, 1); + TEST_COMPARE_BLOB (e->alias_members[0], strlen (e->alias_members[0]), + "alias1", strlen ("alias1")); + break; + + case 1: + TEST_COMPARE (e->alias_members_len, 2); + TEST_COMPARE_BLOB (e->alias_members[0], strlen (e->alias_members[0]), + "alias1a", strlen ("alias1a")); + TEST_COMPARE_BLOB (e->alias_members[1], strlen (e->alias_members[1]), + "alias2", strlen ("alias2")); + break; + + case 2: + TEST_COMPARE (e->alias_members_len, 2); + TEST_COMPARE_BLOB (e->alias_members[0], strlen (e->alias_members[0]), + "comment1", strlen ("comment1")); + TEST_COMPARE_BLOB (e->alias_members[1], strlen (e->alias_members[1]), + "comment2", strlen ("comment2")); + break; + + case 3: + TEST_COMPARE (e->alias_members_len, many_aliases); + for (int i = 0; i < e->alias_members_len; ++i) + { + char alias[30]; + int len = snprintf (alias, sizeof (alias), "a%d", i); + TEST_VERIFY_EXIT (len > 0); + TEST_COMPARE_BLOB (e->alias_members[i], strlen (e->alias_members[i]), + alias, len); + } + break; + } +} + +static int +do_test (void) +{ + /* Make sure we don't try to load the module in the chroot. */ + if (dlopen (LIBNSS_FILES_SO, RTLD_NOW) == NULL) + FAIL_EXIT1 ("could not load " LIBNSS_FILES_SO ": %s", dlerror ()); + + /* Some of these descriptors will become unavailable if there is a + file descriptor leak. 10 is chosen somewhat arbitrarily. The + array must be longer than the number of files opened by nss_files + at the same time (currently that number is 2). */ + int next_descriptors[10]; + for (size_t i = 0; i < array_length (next_descriptors); ++i) + { + next_descriptors[i] = dup (0); + TEST_VERIFY_EXIT (next_descriptors[i] > 0); + } + for (size_t i = 0; i < array_length (next_descriptors); ++i) + xclose (next_descriptors[i]); + + support_become_root (); + if (!support_can_chroot ()) + return EXIT_UNSUPPORTED; + + __nss_configure_lookup ("aliases", "files"); + + xchroot (chroot_env->path_chroot); + + /* Attempt various buffer sizes. If the operation succeeds, we + expect correct data. */ + for (int id = 0; id < array_length (users); ++id) + { + bool found = false; + for (size_t size = 1; size <= 1000; ++size) + { + void *buffer = malloc (size); + struct aliasent result; + struct aliasent *res; + errno = EINVAL; + int ret = getaliasbyname_r (users[id], &result, buffer, size, &res); + if (ret == 0) + { + if (res != NULL) + { + found = true; + check_aliases (id, res); + } + else + { + support_record_failure (); + printf ("error: failed lookup for user \"%s\", size %zu\n", + users[id], size); + } + } + else if (ret != ERANGE) + { + support_record_failure (); + printf ("error: invalid return code %d (user \%s\", size %zu)\n", + ret, users[id], size); + } + free (buffer); + + /* Make sure that we did not have a file descriptor leak. */ + for (size_t i = 0; i < array_length (next_descriptors); ++i) + { + int new_fd = dup (0); + if (new_fd != next_descriptors[i]) + { + support_record_failure (); + printf ("error: descriptor %d at index %zu leaked" + " (user \"%s\", size %zu)\n", + next_descriptors[i], i, users[id], size); + + /* Close unexpected descriptor, the leak probing + descriptors, and the leaked descriptor + next_descriptors[i]. */ + xclose (new_fd); + for (size_t j = 0; j <= i; ++j) + xclose (next_descriptors[j]); + goto next_size; + } + } + for (size_t i = 0; i < array_length (next_descriptors); ++i) + xclose (next_descriptors[i]); + + next_size: + ; + } + if (!found) + { + support_record_failure (); + printf ("error: user %s not found\n", users[id]); + } + } + + support_chroot_free (chroot_env); + return 0; +} + +#define PREPARE prepare +#include