[PATCH 3/7] nss: Add __nss_generic_copy and __nss_generic_dup functions

Florian Weimer fweimer@redhat.com
Mon Nov 24 19:47:54 GMT 2025


So far for struct group only.  These functions will be used internally
in the type-generic NSS implementation, to preserve intermediate
results and to produce the final *_r result.
---
 nss/Makefile               |  10 ++-
 nss/nss-lookups.def        |  26 +++++++
 nss/nss_generic.h          |  61 +++++++++++++++++
 nss/nss_generic_copy.c     |  90 +++++++++++++++++++++++++
 nss/nss_generic_dup.c      |  88 ++++++++++++++++++++++++
 nss/tst-nss_generic_copy.c | 131 ++++++++++++++++++++++++++++++++++++
 nss/tst-nss_generic_dup.c  | 134 +++++++++++++++++++++++++++++++++++++
 7 files changed, 539 insertions(+), 1 deletion(-)
 create mode 100644 nss/nss-lookups.def
 create mode 100644 nss/nss_generic.h
 create mode 100644 nss/nss_generic_copy.c
 create mode 100644 nss/nss_generic_dup.c
 create mode 100644 nss/tst-nss_generic_copy.c
 create mode 100644 nss/tst-nss_generic_dup.c

diff --git a/nss/Makefile b/nss/Makefile
index 07556a5958..745a328d6e 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -45,6 +45,8 @@ routines = \
   nss_files_data \
   nss_files_fopen \
   nss_files_functions \
+  nss_generic_copy \
+  nss_generic_dup \
   nss_hash \
   nss_module \
   nss_parse_line_result \
@@ -302,10 +304,16 @@ makedb-modules = xmalloc hash-string
 others-extras		= $(makedb-modules)
 extra-objs		+= $(makedb-modules:=.o)
 
-tests-static            = tst-field
+tests-static = \
+  tst-field \
+  tst-nss_generic_copy \
+  tst-nss_generic_dup \
+  # tests-static
 
 tests-internal := \
   tst-field \
+  tst-nss_generic_copy \
+  tst-nss_generic_dup \
   tst-rfc3484 \
   tst-rfc3484-2 \
   tst-rfc3484-3 \
diff --git a/nss/nss-lookups.def b/nss/nss-lookups.def
new file mode 100644
index 0000000000..012d67fbae
--- /dev/null
+++ b/nss/nss-lookups.def
@@ -0,0 +1,26 @@
+/* Lookup type definitions for NSS.
+   Copyright (C) 2025 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/>.  */
+
+DEFINE_LOOKUP (getgrgid, group, "getgrgid_r")
+DEFINE_LOOKUP (getgrnam, group, "getgrnam_r")
+
+/*
+   Local Variables:
+    mode:C
+   End:
+ */
diff --git a/nss/nss_generic.h b/nss/nss_generic.h
new file mode 100644
index 0000000000..820169ca12
--- /dev/null
+++ b/nss/nss_generic.h
@@ -0,0 +1,61 @@
+/* Type- and query-agonstic NSS functionality.
+   Copyright (C) 2025 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/>.  */
+
+/* The functions declared in this file use void * instead of concrete
+   pointer types such as struct group and NSS service module function
+   pointers.  They can be combined to implement different high-level,
+   type-safe functions.  */
+
+
+#ifndef NSS_GENERIC_H
+#define NSS_GENERIC_H
+
+#include <nsswitch.h>
+
+enum nss_lookup_type
+  {
+#define DEFINE_LOOKUP(name, database, function_1) nss_lookup_##name,
+#include "nss-lookups.def"
+#undef DEFINE_LOOKUP
+  };
+
+/* Define the nss_lookup_MAX constant.  This is not part of enum
+   nss_lookup_type so that switch coverage warnings work.  */
+enum
+  {
+#define DEFINE_LOOKUP(name, database, function_1) HIDDEN_nss_lookup_##name,
+#include "nss-lookups.def"
+#undef DEFINE_LOOKUP
+    nss_lookup_MAX
+  };
+
+/* Copy an NSS struct corresponding to LT into DEST, potentially using
+   additional storage of LENGTH bytes at BUFFER.  If BUFFER is not
+   large enough, return ERANGE and set errno to ERANGE.  Otherwise
+   return zero.  */
+int __nss_generic_copy (enum nss_lookup_type lt, const void *source,
+                        void *dest, char *buffer, size_t length)
+  attribute_hidden;
+
+/* Create a malloc-allocated copy of the NSS struct of type LT at
+   SOURCE and return a pointer to it.  Return NULL on allocation
+   failure.  */
+void *__nss_generic_dup (enum nss_lookup_type lt, const void *source)
+  attribute_hidden;
+
+#endif /* NSS_GENERIC */
diff --git a/nss/nss_generic_copy.c b/nss/nss_generic_copy.c
new file mode 100644
index 0000000000..78945b79c3
--- /dev/null
+++ b/nss/nss_generic_copy.c
@@ -0,0 +1,90 @@
+/* Copy an NSS struct into a fixed-size destination buffer.
+   Copyright (C) 2025 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 <alloc_buffer.h>
+#include <errno.h>
+#include <grp.h>
+#include <nss_generic.h>
+
+static char *
+safe_copy_string (struct alloc_buffer *buf, const char *source)
+{
+  if (source == NULL)
+    return NULL;
+  else
+    return alloc_buffer_copy_string (buf, source);
+}
+
+static void
+__nss_copy_grp (enum nss_lookup_type lt, const struct group *source,
+                struct group *result, struct alloc_buffer *buf)
+{
+  *result = *source;
+
+  size_t member_count = 0;
+  if (source->gr_mem != 0)
+    {
+      while (source->gr_mem[member_count] != NULL)
+        ++member_count;
+
+      /* Copy the array first, to minimize the alignment requirements.  */
+      result->gr_mem = alloc_buffer_alloc_array (buf, char *, member_count + 1);
+      if (result->gr_mem == NULL)
+        return;
+
+      for (size_t i = 0; i < member_count; ++i)
+        result->gr_mem[i]
+          = alloc_buffer_copy_string (buf, source->gr_mem[i]);
+
+      result->gr_mem[member_count] = NULL;
+    }
+
+  result->gr_name = safe_copy_string (buf, source->gr_name);
+  result->gr_passwd = safe_copy_string (buf, source->gr_passwd);
+}
+
+static void
+__nss_do_copy (enum nss_lookup_type lt, const void *source, void *result,
+               struct alloc_buffer *buf)
+{
+  switch (lt)
+    {
+    case nss_lookup_getgrgid:
+    case nss_lookup_getgrnam:
+      return __nss_copy_grp (lt, source, result, buf);
+    }
+  __builtin_unreachable ();
+}
+
+int
+__nss_generic_copy (enum nss_lookup_type lt, const void *source,
+                    void *result, char *buffer, size_t length)
+{
+  struct alloc_buffer buf = alloc_buffer_create (buffer, length);
+
+  __nss_do_copy (lt, source, result, &buf);
+
+  /* Check if any allocation failed.  */
+  if (alloc_buffer_has_failed (&buf))
+    {
+      __set_errno (ERANGE);
+      return ERANGE;
+    }
+
+  return 0;
+}
diff --git a/nss/nss_generic_dup.c b/nss/nss_generic_dup.c
new file mode 100644
index 0000000000..44bf4cbb3e
--- /dev/null
+++ b/nss/nss_generic_dup.c
@@ -0,0 +1,88 @@
+/* Duplicate struct group data with a single malloc allocation.
+   Copyright (C) 2025 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 <assert.h>
+#include <alloc_buffer.h>
+#include <errno.h>
+#include <grp.h>
+#include <nss_generic.h>
+#include <string.h>
+
+static size_t
+safe_strlen_null (const char *source)
+{
+  if (source == NULL)
+    return 0;
+  else
+    return strlen (source) + 1;
+}
+
+static size_t
+__nss_group_buffer_size (const struct group *source)
+{
+  size_t size = sizeof (*source);
+  size += safe_strlen_null (source->gr_name);
+  size += safe_strlen_null (source->gr_passwd);
+
+  /* Assume that the array is allocated first, so that no
+     alignment is needed.  */
+  if (source->gr_mem != NULL)
+    {
+      for (size_t i = 0; source->gr_mem[i] != NULL; ++i)
+        size += sizeof (char *) + strlen (source->gr_mem[i]) + 1;
+      size += sizeof (char *);
+    }
+
+  return size;
+}
+
+static size_t
+__nss_buffer_size (enum nss_lookup_type lt, const void *source)
+{
+  switch (lt)
+    {
+    case nss_lookup_getgrgid:
+    case nss_lookup_getgrnam:
+      return __nss_group_buffer_size (source);
+    }
+
+  __builtin_unreachable ();
+}
+
+void *
+__nss_generic_dup (enum nss_lookup_type lt, const void *source)
+{
+  struct group *result;
+  char *buf;
+  char *end;
+  {
+    size_t size = __nss_buffer_size (lt, source);
+    result = malloc (size);
+    if (result == NULL)
+      return NULL;
+    buf = (char *) (result + 1);
+    end = (char *) result + size;
+  }
+
+  /* Assert that the computed size was correct.  */
+  int ret __attribute__ ((unused))
+    = __nss_generic_copy (lt, source, result, buf, end - buf);
+  assert (ret == 0);
+
+  return result;
+}
diff --git a/nss/tst-nss_generic_copy.c b/nss/tst-nss_generic_copy.c
new file mode 100644
index 0000000000..c1c8af3f25
--- /dev/null
+++ b/nss/tst-nss_generic_copy.c
@@ -0,0 +1,131 @@
+/* Test program for the __nss_generic_copy function.
+   Copyright (C) 2025 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/>.  */
+
+/* This test needs to be statically linked because it accesses the
+   hidden function __nss_generic_copy.  */
+
+#include <errno.h>
+#include <grp.h>
+#include <nss_generic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+
+static void
+test_group (enum nss_lookup_type lt)
+{
+  char *members[4] = { (char *) "user1", (char *) "user2", (char *) "user3", };
+  struct group source =
+    {
+      .gr_name = (char *) "group-name",
+      .gr_passwd = (char *) "password",
+      .gr_gid = 1000,
+      .gr_mem = members
+    };
+
+  /* Test with sufficient buffer size.  */
+  {
+    char buffer[1024];
+    struct group result;
+
+    errno = 23587;
+    TEST_COMPARE (__nss_generic_copy (lt, &source, &result,
+                                      buffer, sizeof (buffer)),
+                  0);
+    TEST_VERIFY (errno != 0);
+
+    /* Verify the copied data */
+    TEST_COMPARE_STRING (result.gr_name, "group-name");
+    TEST_COMPARE_STRING (result.gr_passwd, "password");
+    TEST_COMPARE (result.gr_gid, 1000);
+    TEST_COMPARE_STRING (result.gr_mem[0], "user1");
+    TEST_COMPARE_STRING (result.gr_mem[1], "user2");
+    TEST_COMPARE_STRING (result.gr_mem[2], "user3");
+    TEST_COMPARE_STRING (result.gr_mem[3], NULL);
+  }
+
+  /* Test with insufficient buffer size.  */
+  {
+    char buffer[10];
+    struct group result;
+
+    errno = 23587;
+    int ret = __nss_generic_copy (lt, &source,
+                                  &result, buffer, sizeof (buffer));
+    TEST_COMPARE (ret, ERANGE);
+    TEST_COMPARE (errno, ERANGE);
+  }
+
+  /* Test with NULL members.  */
+  {
+    source.gr_name = NULL;
+    source.gr_passwd = NULL;
+    source.gr_mem = NULL;
+
+    char buffer[1024];
+    struct group result;
+    memset (&result, 0xcc, sizeof (result));
+
+    errno = 23587;
+    TEST_COMPARE (__nss_generic_copy (lt, &source, &result,
+                                      buffer, sizeof (buffer)),
+                  0);
+    TEST_VERIFY (errno != 0);
+    TEST_COMPARE_STRING (result.gr_name, NULL);
+    TEST_COMPARE_STRING (result.gr_passwd, NULL);
+    TEST_COMPARE (result.gr_gid, 1000);
+    TEST_VERIFY (result.gr_mem == NULL);
+  }
+
+  /* Test with empty strings.  */
+  {
+    char empty[] = "";
+    char *list[] = { empty, NULL };
+    source.gr_name = (char *) "";
+    source.gr_passwd = (char *) "";
+    source.gr_mem = list;
+
+    char buffer[1024];
+    struct group result;
+
+    errno = 23587;
+    TEST_COMPARE (__nss_generic_copy (lt, &source, &result,
+                                      buffer, sizeof (buffer)),
+                  0);
+    TEST_VERIFY (errno != 0);
+
+    /* Verify the copied data */
+    TEST_COMPARE_STRING (result.gr_name, "");
+    TEST_COMPARE_STRING (result.gr_passwd, "");
+    TEST_COMPARE (result.gr_gid, 1000);
+    TEST_COMPARE_STRING (result.gr_mem[0], "");
+    TEST_COMPARE_STRING (result.gr_mem[1], NULL);
+  }
+}
+
+static int
+do_test (void)
+{
+  test_group (nss_lookup_getgrgid);
+  test_group (nss_lookup_getgrnam);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/nss/tst-nss_generic_dup.c b/nss/tst-nss_generic_dup.c
new file mode 100644
index 0000000000..aff5789433
--- /dev/null
+++ b/nss/tst-nss_generic_dup.c
@@ -0,0 +1,134 @@
+/* Test program for the __nss_generic_dup function.
+   Copyright (C) 2025 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/>.  */
+
+/* This test needs to be statically linked because it accesses the
+   hidden function __nss_generic_dup.  */
+
+#include <errno.h>
+#include <grp.h>
+#include <malloc.h>
+#include <nss_generic.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+
+static void
+check_pointer_in_allocation (void *allocation, void *ptr)
+{
+  TEST_VERIFY (ptr >= allocation);
+  TEST_VERIFY (ptr <  (void *) ((char *) allocation
+                                + malloc_usable_size (allocation)));
+}
+
+static void
+test_group (enum nss_lookup_type lt)
+{
+  char *members[4] = { (char *) "user1", (char *) "user2",
+                       (char *) "long-user-3-name" };
+  struct group source =
+    {
+      .gr_name = (char *) "group-name",
+      .gr_passwd = (char *) "password",
+      .gr_gid = 1000,
+      .gr_mem = members
+    };
+
+  /* Test with normal group data.  */
+  {
+    struct group *result = __nss_generic_dup (lt, &source);
+    TEST_VERIFY (result != NULL);
+
+    /* Verify the copied data */
+    TEST_COMPARE_STRING (result->gr_name, "group-name");
+    TEST_COMPARE_STRING (result->gr_passwd, "password");
+    TEST_COMPARE (result->gr_gid, 1000);
+    TEST_COMPARE_STRING (result->gr_mem[0], "user1");
+    TEST_COMPARE_STRING (result->gr_mem[1], "user2");
+    TEST_COMPARE_STRING (result->gr_mem[2], "long-user-3-name");
+    TEST_VERIFY (result->gr_mem[3] == NULL);
+
+    check_pointer_in_allocation (result, result->gr_name);
+    check_pointer_in_allocation (result, result->gr_passwd);
+    check_pointer_in_allocation (result, result->gr_mem);
+    for (int i = 0; result->gr_mem[i] != NULL; i++)
+      check_pointer_in_allocation (result, result->gr_mem[i]);
+
+    free (result);
+  }
+
+  /* Test with NULL members.  */
+  {
+    struct group source_null =
+      {
+        .gr_gid = 2000,
+      };
+
+    struct group *result = __nss_generic_dup (lt, &source_null);
+    TEST_VERIFY (result != NULL);
+
+    TEST_VERIFY (result->gr_name == NULL);
+    TEST_VERIFY (result->gr_passwd == NULL);
+    TEST_COMPARE (result->gr_gid, 2000);
+    TEST_VERIFY (result->gr_mem == NULL);
+
+    free (result);
+  }
+
+  /* Test with empty strings.  */
+  {
+    char empty[] = "";
+    char *empty_list[] = { empty, NULL };
+    struct group source_empty =
+      {
+        .gr_name = (char *) "",
+        .gr_passwd = (char *) "",
+        .gr_gid = 3000,
+        .gr_mem = empty_list
+      };
+
+    struct group *result = __nss_generic_dup (lt, &source_empty);
+    TEST_VERIFY (result != NULL);
+
+    TEST_COMPARE_STRING (result->gr_name, "");
+    TEST_COMPARE_STRING (result->gr_passwd, "");
+    TEST_COMPARE (result->gr_gid, 3000);
+    TEST_COMPARE_STRING (result->gr_mem[0], "");
+    TEST_VERIFY (result->gr_mem[1] == NULL);
+
+    check_pointer_in_allocation (result, result->gr_name);
+    check_pointer_in_allocation (result, result->gr_passwd);
+    check_pointer_in_allocation (result, result->gr_mem);
+    for (int i = 0; result->gr_mem[i] != NULL; i++)
+      check_pointer_in_allocation (result, result->gr_mem[i]);
+
+    free (result);
+  }
+}
+
+static int
+do_test (void)
+{
+  test_group (nss_lookup_getgrgid);
+  test_group (nss_lookup_getgrnam);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
-- 
2.52.0




More information about the Libc-alpha mailing list