[PATCH] nss: Add missing failure check to __nss_database_get (bug 28940)

Florian Weimer fweimer@redhat.com
Fri Dec 20 11:41:18 GMT 2024


This avoids a null pointer dereference in the
nss_database_check_reload_and_get function.

Tested on aarch64-linux-gnu, i686-linux-gnu, x86_64-linux-gnu.

---
 nss/Makefile                 |   1 +
 nss/nss_database.c           |   2 +
 nss/tst-nss-malloc-failure.c | 217 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 220 insertions(+)

diff --git a/nss/Makefile b/nss/Makefile
index 9331b3308c..a2a2031cd2 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -326,6 +326,7 @@ tests := \
   tst-gshadow \
   tst-nss-getpwent \
   tst-nss-hash \
+  tst-nss-malloc-failure \
   tst-nss-test1 \
   tst-nss-test2 \
   tst-nss-test4 \
diff --git a/nss/nss_database.c b/nss/nss_database.c
index efe77aeaff..920f07d5cd 100644
--- a/nss/nss_database.c
+++ b/nss/nss_database.c
@@ -478,6 +478,8 @@ bool
 __nss_database_get (enum nss_database db, nss_action_list *actions)
 {
   struct nss_database_state *local = nss_database_state_get ();
+  if (local == NULL)
+    return false;
   return nss_database_check_reload_and_get (local, actions, db);
 }
 libc_hidden_def (__nss_database_get)
diff --git a/nss/tst-nss-malloc-failure.c b/nss/tst-nss-malloc-failure.c
new file mode 100644
index 0000000000..b26e614bc8
--- /dev/null
+++ b/nss/tst-nss-malloc-failure.c
@@ -0,0 +1,217 @@
+/* Test NSS with injected allocation failures (bug 28940).
+   Copyright (C) 2024 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
+   <https://www.gnu.org/licenses/>.  */
+
+
+#include <errno.h>
+#include <malloc.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/support.h>
+#include <support/xstdio.h>
+#include <unistd.h>
+
+/* This test calls getwpuid_r via getlogin_r (on Linux).
+
+   This test uses the NSS system configuration to exercise that code
+   path.  It means that it can fail (crash) if malloc failure is not
+   handled by NSS modules for the passwd database.  */
+
+/* Data structure allocated via MAP_SHARED, so that writes from the
+   subprocess are visible.  */
+struct shared_data
+{
+  /* Number of tracked allocations performed so far.  */
+  volatile unsigned int allocation_count;
+
+  /* If this number is reached, one allocation fails.  */
+  volatile unsigned int failing_allocation;
+
+  /* The subprocess stores the expected name here.  */
+  char name[100];
+};
+
+/* Allocation count in shared mapping.  */
+static struct shared_data *shared;
+
+/* Returns true if a failure should be injected for this allocation.  */
+static bool
+fail_this_allocation (void)
+{
+  if (shared != NULL)
+    {
+      unsigned int count = shared->allocation_count;
+      shared->allocation_count = count + 1;
+      return count == shared->failing_allocation;
+    }
+  else
+    return false;
+}
+
+/* Failure-injecting wrappers for allocation functions used by glibc.  */
+
+void *
+malloc (size_t size)
+{
+  if (fail_this_allocation ())
+    {
+      errno = ENOMEM;
+      return NULL;
+    }
+  extern __typeof (malloc) __libc_malloc;
+  return __libc_malloc (size);
+}
+
+void *
+calloc (size_t a, size_t b)
+{
+  if (fail_this_allocation ())
+    {
+      errno = ENOMEM;
+      return NULL;
+    }
+  extern __typeof (calloc) __libc_calloc;
+  return __libc_calloc (a, b);
+}
+
+void *
+realloc (void *ptr, size_t size)
+{
+  if (fail_this_allocation ())
+    {
+      /* Only fail if realloc actually reallocates.  If it shrinks
+         memory, allocation failure is not really valid.  */
+      if (ptr == NULL || malloc_usable_size (ptr) < size)
+        {
+          errno = ENOMEM;
+          return NULL;
+        }
+    }
+  extern __typeof (realloc) __libc_realloc;
+  return __libc_realloc (ptr, size);
+}
+
+/* No-op subprocess to verify that support_isolate_in_subprocess does
+   not perform any heap allocations.  */
+static void
+no_op (void *ignored)
+{
+}
+
+/* Perform a getlogin_r call in a subprocess, to obtain the number of
+   allocations used and the expected result of a successful call.  */
+static void
+initialize (void *ignored)
+{
+  {
+    FILE *fp = fopen (_PATH_NSSWITCH_CONF, "r");
+    if (fp == NULL)
+      printf ("info: no %s file\n", _PATH_NSSWITCH_CONF);
+    else
+      {
+        printf ("info: %s contents follows\n", _PATH_NSSWITCH_CONF);
+        int last_ch = '\n';
+        while (true)
+          {
+            int ch = fgetc (fp);
+            if (ch == EOF)
+              break;
+            putchar (ch);
+            last_ch = ch;
+          }
+        if (last_ch != '\n')
+          putchar ('\n');
+        printf ("(end of %s contents)\n", _PATH_NSSWITCH_CONF);
+        xfclose (fp);
+      }
+  }
+
+  shared->allocation_count = 0;
+  if (getlogin_r (shared->name, sizeof (shared->name)) != 0)
+    {
+      printf ("warning: getlogin_r failed: %s (%d)\n",
+              strerrorname_np (errno), errno);
+      shared->name[0] = '\0';
+    }
+
+}
+
+/* Perform getlogin_r in a subprocess with fault injection.  */
+static void
+test_in_subprocess (void *ignored)
+{
+  unsigned int inject_at = shared->failing_allocation;
+  char name[sizeof (shared->name)];
+  int ret = getlogin_r (name, sizeof (name));
+  shared->failing_allocation = ~0U;
+  if (ret == 0)
+    TEST_COMPARE_STRING (name, shared->name);
+  else
+    printf ("info: allocation %u failure results in error %s (%d)\n",
+            inject_at, strerrorname_np (errno), errno);
+
+  /* The second call should succeed.  */
+  ret = getlogin_r (name, sizeof (name));
+  TEST_COMPARE (ret, 0);
+  if (ret == 0)
+    TEST_COMPARE_STRING (name, shared->name);
+}
+
+static int
+do_test (void)
+{
+  shared = support_shared_allocate (sizeof (*shared));
+
+  /* Disable fault injection.  */
+  shared->failing_allocation = ~0U;
+
+  support_isolate_in_subprocess (no_op, NULL);
+  TEST_COMPARE (shared->allocation_count, 0);
+
+  support_isolate_in_subprocess (initialize, NULL);
+
+  if (shared->name[0] == '\0')
+    FAIL_UNSUPPORTED ("getlogin_r did not succeed");
+
+  /* The number of allocations in the successful case, plus some
+     slack.  Once the number of expected allocations is exceeded,
+     injecting further failures does not make a difference.  */
+  unsigned int maximum_allocation_count = shared->allocation_count;
+  printf ("info: successfull call performs %u allocations\n",
+          maximum_allocation_count);
+  maximum_allocation_count += 10;
+
+  for (unsigned int inject_at = 0; inject_at <= maximum_allocation_count;
+       ++inject_at)
+    {
+      shared->allocation_count = 0;
+      shared->failing_allocation = inject_at;
+      support_isolate_in_subprocess (test_in_subprocess, NULL);
+    }
+
+  struct shared_data *shared_2 = shared;
+  shared = NULL;
+  support_shared_free (shared_2);
+
+  return 0;
+}
+
+#include <support/test-driver.c>

base-commit: 6fba7d657872c9218af49a789322de3882054b2c



More information about the Libc-alpha mailing list