08c3a6
commit 54b12733959238204d7b0e46e69fc7f7d8890b20
08c3a6
Author: Florian Weimer <fweimer@redhat.com>
08c3a6
Date:   Fri Mar 11 08:23:56 2022 +0100
08c3a6
08c3a6
    nss: Protect against errno changes in function lookup (bug 28953)
08c3a6
    
08c3a6
    dlopen may clobber errno.  The nss_test_errno module uses an ELF
08c3a6
    constructor to achieve that, but there could be internal errors
08c3a6
    during dlopen that cause this, too.  Therefore, the NSS framework
08c3a6
    has to guard against such errno clobbers.
08c3a6
    
08c3a6
    __nss_module_get_function is currently the only function that calls
08c3a6
    __nss_module_load, so it is sufficient to save and restore errno
08c3a6
    around this call.
08c3a6
    
08c3a6
    Reviewed-by: Carlos O'Donell <carlos@redhat.com>
08c3a6
    (cherry picked from commit 9bdf92c79d63b42f931101bb6df87129c408b0c4)
08c3a6
08c3a6
diff --git a/nss/Makefile b/nss/Makefile
08c3a6
index e223243d9d62041c..716bc8f6ef5276b0 100644
08c3a6
--- a/nss/Makefile
08c3a6
+++ b/nss/Makefile
08c3a6
@@ -60,7 +60,8 @@ tests			= test-netdb test-digits-dots tst-nss-getpwent bug17079 \
08c3a6
 			  tst-nss-test1 \
08c3a6
 			  tst-nss-test2 \
08c3a6
 			  tst-nss-test4 \
08c3a6
-			  tst-nss-test5
08c3a6
+			  tst-nss-test5 \
08c3a6
+			  tst-nss-test_errno
08c3a6
 xtests			= bug-erange
08c3a6
 
08c3a6
 tests-container = \
08c3a6
@@ -132,7 +133,7 @@ libnss_compat-inhibit-o	= $(filter-out .os,$(object-suffixes))
08c3a6
 ifeq ($(build-static-nss),yes)
08c3a6
 tests-static		+= tst-nss-static
08c3a6
 endif
08c3a6
-extra-test-objs		+= nss_test1.os nss_test2.os
08c3a6
+extra-test-objs		+= nss_test1.os nss_test2.os nss_test_errno.os
08c3a6
 
08c3a6
 include ../Rules
08c3a6
 
08c3a6
@@ -166,19 +167,26 @@ rtld-tests-LDFLAGS += -Wl,--dynamic-list=nss_test.ver
08c3a6
 
08c3a6
 libof-nss_test1 = extramodules
08c3a6
 libof-nss_test2 = extramodules
08c3a6
+libof-nss_test_errno = extramodules
08c3a6
 $(objpfx)/libnss_test1.so: $(objpfx)nss_test1.os $(link-libc-deps)
08c3a6
 	$(build-module)
08c3a6
 $(objpfx)/libnss_test2.so: $(objpfx)nss_test2.os $(link-libc-deps)
08c3a6
 	$(build-module)
08c3a6
+$(objpfx)/libnss_test_errno.so: $(objpfx)nss_test_errno.os $(link-libc-deps)
08c3a6
+	$(build-module)
08c3a6
 $(objpfx)nss_test2.os : nss_test1.c
08c3a6
 # Use the nss_files suffix for these objects as well.
08c3a6
 $(objpfx)/libnss_test1.so$(libnss_files.so-version): $(objpfx)/libnss_test1.so
08c3a6
 	$(make-link)
08c3a6
 $(objpfx)/libnss_test2.so$(libnss_files.so-version): $(objpfx)/libnss_test2.so
08c3a6
 	$(make-link)
08c3a6
+$(objpfx)/libnss_test_errno.so$(libnss_files.so-version): \
08c3a6
+  $(objpfx)/libnss_test_errno.so
08c3a6
+	$(make-link)
08c3a6
 $(patsubst %,$(objpfx)%.out,$(tests) $(tests-container)) : \
08c3a6
 	$(objpfx)/libnss_test1.so$(libnss_files.so-version) \
08c3a6
-	$(objpfx)/libnss_test2.so$(libnss_files.so-version)
08c3a6
+	$(objpfx)/libnss_test2.so$(libnss_files.so-version) \
08c3a6
+	$(objpfx)/libnss_test_errno.so$(libnss_files.so-version)
08c3a6
 
08c3a6
 ifeq (yes,$(have-thread-library))
08c3a6
 $(objpfx)tst-cancel-getpwuid_r: $(shared-thread-library)
08c3a6
diff --git a/nss/nss_module.c b/nss/nss_module.c
08c3a6
index b28cb94a6a0aeb41..3a4a464256121e41 100644
08c3a6
--- a/nss/nss_module.c
08c3a6
+++ b/nss/nss_module.c
08c3a6
@@ -330,8 +330,18 @@ name_search (const void *left, const void *right)
08c3a6
 void *
08c3a6
 __nss_module_get_function (struct nss_module *module, const char *name)
08c3a6
 {
08c3a6
+  /* A successful dlopen might clobber errno.   */
08c3a6
+  int saved_errno = errno;
08c3a6
+
08c3a6
   if (!__nss_module_load (module))
08c3a6
-    return NULL;
08c3a6
+    {
08c3a6
+      /* Reporting module load failure is currently inaccurate.  See
08c3a6
+	 bug 22041.  Not changing errno is the conservative choice.  */
08c3a6
+      __set_errno (saved_errno);
08c3a6
+      return NULL;
08c3a6
+    }
08c3a6
+
08c3a6
+  __set_errno (saved_errno);
08c3a6
 
08c3a6
   function_name *name_entry = bsearch (name, nss_function_name_array,
08c3a6
                                        array_length (nss_function_name_array),
08c3a6
diff --git a/nss/nss_test_errno.c b/nss/nss_test_errno.c
08c3a6
new file mode 100644
08c3a6
index 0000000000000000..680f8a07b97fe263
08c3a6
--- /dev/null
08c3a6
+++ b/nss/nss_test_errno.c
08c3a6
@@ -0,0 +1,58 @@
08c3a6
+/* NSS service provider with errno clobber.
08c3a6
+   Copyright (C) 2022 Free Software Foundation, Inc.
08c3a6
+   This file is part of the GNU C Library.
08c3a6
+
08c3a6
+   The GNU C Library is free software; you can redistribute it and/or
08c3a6
+   modify it under the terms of the GNU Lesser General Public
08c3a6
+   License as published by the Free Software Foundation; either
08c3a6
+   version 2.1 of the License, or (at your option) any later version.
08c3a6
+
08c3a6
+   The GNU C Library is distributed in the hope that it will be useful,
08c3a6
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
08c3a6
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
08c3a6
+   Lesser General Public License for more details.
08c3a6
+
08c3a6
+   You should have received a copy of the GNU Lesser General Public
08c3a6
+   License along with the GNU C Library; if not, see
08c3a6
+   <https://www.gnu.org/licenses/>.  */
08c3a6
+
08c3a6
+#include <errno.h>
08c3a6
+#include <nss.h>
08c3a6
+#include <stdlib.h>
08c3a6
+
08c3a6
+/* Catch misnamed and functions.  */
08c3a6
+#pragma GCC diagnostic error "-Wmissing-prototypes"
08c3a6
+NSS_DECLARE_MODULE_FUNCTIONS (test_errno)
08c3a6
+
08c3a6
+static void __attribute__ ((constructor))
08c3a6
+init (void)
08c3a6
+{
08c3a6
+  /* An arbitrary error code which is otherwise not used.  */
08c3a6
+  errno = ELIBBAD;
08c3a6
+}
08c3a6
+
08c3a6
+/* Lookup functions for pwd follow that do not return any data.  */
08c3a6
+
08c3a6
+/* Catch misnamed function definitions.  */
08c3a6
+
08c3a6
+enum nss_status
08c3a6
+_nss_test_errno_setpwent (int stayopen)
08c3a6
+{
08c3a6
+  setenv ("_nss_test_errno_setpwent", "yes", 1);
08c3a6
+  return NSS_STATUS_SUCCESS;
08c3a6
+}
08c3a6
+
08c3a6
+enum nss_status
08c3a6
+_nss_test_errno_getpwent_r (struct passwd *result,
08c3a6
+                            char *buffer, size_t size, int *errnop)
08c3a6
+{
08c3a6
+  setenv ("_nss_test_errno_getpwent_r", "yes", 1);
08c3a6
+  return NSS_STATUS_NOTFOUND;
08c3a6
+}
08c3a6
+
08c3a6
+enum nss_status
08c3a6
+_nss_test_errno_endpwent (void)
08c3a6
+{
08c3a6
+  setenv ("_nss_test_errno_endpwent", "yes", 1);
08c3a6
+  return NSS_STATUS_SUCCESS;
08c3a6
+}
08c3a6
diff --git a/nss/tst-nss-test_errno.c b/nss/tst-nss-test_errno.c
08c3a6
new file mode 100644
08c3a6
index 0000000000000000..d2c42dd363a38b0e
08c3a6
--- /dev/null
08c3a6
+++ b/nss/tst-nss-test_errno.c
08c3a6
@@ -0,0 +1,61 @@
08c3a6
+/* getpwent failure when dlopen clobbers errno (bug 28953).
08c3a6
+   Copyright (C) 2022 Free Software Foundation, Inc.
08c3a6
+   This file is part of the GNU C Library.
08c3a6
+
08c3a6
+   The GNU C Library is free software; you can redistribute it and/or
08c3a6
+   modify it under the terms of the GNU Lesser General Public
08c3a6
+   License as published by the Free Software Foundation; either
08c3a6
+   version 2.1 of the License, or (at your option) any later version.
08c3a6
+
08c3a6
+   The GNU C Library is distributed in the hope that it will be useful,
08c3a6
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
08c3a6
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
08c3a6
+   Lesser General Public License for more details.
08c3a6
+
08c3a6
+   You should have received a copy of the GNU Lesser General Public
08c3a6
+   License along with the GNU C Library; if not, see
08c3a6
+   <https://www.gnu.org/licenses/>.  */
08c3a6
+
08c3a6
+#include <nss.h>
08c3a6
+#include <support/check.h>
08c3a6
+#include <stdlib.h>
08c3a6
+#include <errno.h>
08c3a6
+#include <stdbool.h>
08c3a6
+#include <pwd.h>
08c3a6
+#include <string.h>
08c3a6
+
08c3a6
+static int
08c3a6
+do_test (void)
08c3a6
+{
08c3a6
+  __nss_configure_lookup ("passwd", "files test_errno");
08c3a6
+
08c3a6
+  errno = 0;
08c3a6
+  setpwent ();
08c3a6
+  TEST_COMPARE (errno, 0);
08c3a6
+
08c3a6
+  bool root_seen = false;
08c3a6
+  while (true)
08c3a6
+    {
08c3a6
+      errno = 0;
08c3a6
+      struct passwd *e = getpwent ();
08c3a6
+      if (e == NULL)
08c3a6
+        break;
08c3a6
+      if (strcmp (e->pw_name, "root"))
08c3a6
+        root_seen = true;
08c3a6
+    }
08c3a6
+
08c3a6
+  TEST_COMPARE (errno, 0);
08c3a6
+  TEST_VERIFY (root_seen);
08c3a6
+
08c3a6
+  errno = 0;
08c3a6
+  endpwent ();
08c3a6
+  TEST_COMPARE (errno, 0);
08c3a6
+
08c3a6
+  TEST_COMPARE_STRING (getenv ("_nss_test_errno_setpwent"), "yes");
08c3a6
+  TEST_COMPARE_STRING (getenv ("_nss_test_errno_getpwent_r"), "yes");
08c3a6
+  TEST_COMPARE_STRING (getenv ("_nss_test_errno_endpwent"), "yes");
08c3a6
+
08c3a6
+  return 0;
08c3a6
+}
08c3a6
+
08c3a6
+#include <support/test-driver.c>