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