[v2,07/23] nss: Add __nss_generic_copy and __nss_generic_dup functions

Message ID f2b330b78e7d10e1df2a38c369e3db2c43cc29e6.1774037705.git.fweimer@redhat.com (mailing list archive)
State Failed CI
Headers
Series NSS, nscd updates (for group merging and more) |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 fail Test failed

Commit Message

Florian Weimer March 20, 2026, 8:41 p.m. UTC
  So far for struct group and struct passwd 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               |  11 +-
 nss/nss-lookups.def        |  28 +++++
 nss/nss_generic.h          |  67 +++++++++++
 nss/nss_generic_copy.c     | 120 ++++++++++++++++++
 nss/nss_generic_dup.c      | 125 +++++++++++++++++++
 nss/nss_generic_nscd.c     |  29 +++++
 nss/tst-nss_generic_copy.c | 241 +++++++++++++++++++++++++++++++++++++
 nss/tst-nss_generic_dup.c  | 231 +++++++++++++++++++++++++++++++++++
 8 files changed, 851 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/nss_generic_nscd.c
 create mode 100644 nss/tst-nss_generic_copy.c
 create mode 100644 nss/tst-nss_generic_dup.c
  

Comments

Carlos O'Donell March 23, 2026, 4:42 p.m. UTC | #1
On 3/20/26 4:41 PM, Florian Weimer wrote:
> So far for struct group and struct passwd only.

LGTM. Nice design. The need for a semantic difference between copy and
dup wasn't initially apparent to me, but I see that we need it for a
"caller allocated" buffer versus one we allocate ourselves.

I have one question, which is not a blocker for accepting this change,
but there is an assert in __nss_generic_dup which might go away when
built with -NDEBUG (noted by Claude Code v2.1.81 with Sonnet 4.5).
Do we care about that? Should we make it a runtime abort?

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
  
> 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               |  11 +-
>   nss/nss-lookups.def        |  28 +++++
>   nss/nss_generic.h          |  67 +++++++++++
>   nss/nss_generic_copy.c     | 120 ++++++++++++++++++
>   nss/nss_generic_dup.c      | 125 +++++++++++++++++++
>   nss/nss_generic_nscd.c     |  29 +++++
>   nss/tst-nss_generic_copy.c | 241 +++++++++++++++++++++++++++++++++++++
>   nss/tst-nss_generic_dup.c  | 231 +++++++++++++++++++++++++++++++++++
>   8 files changed, 851 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/nss_generic_nscd.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 42d28cf40a..ec68954f3b 100644
> --- a/nss/Makefile
> +++ b/nss/Makefile
> @@ -45,6 +45,9 @@ routines = \
>     nss_files_data \
>     nss_files_fopen \
>     nss_files_functions \
> +  nss_generic_copy \
> +  nss_generic_dup \
> +  nss_generic_nscd \
>     nss_hash \
>     nss_module \
>     nss_parse_line_result \
> @@ -302,10 +305,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..80ec36d170
> --- /dev/null
> +++ b/nss/nss-lookups.def
> @@ -0,0 +1,28 @@
> +/* Lookup type definitions for NSS.
> +   Copyright (C) 2026 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", grpdb)
> +DEFINE_LOOKUP (getgrnam, group, "getgrnam_r", grpdb)
> +DEFINE_LOOKUP (getpwnam, passwd, "getpwnam_r", pwddb)
> +DEFINE_LOOKUP (getpwuid, passwd, "getpwuid_r", pwddb)
> +
> +/*
> +   Local Variables:
> +    mode:C
> +   End:
> + */
> diff --git a/nss/nss_generic.h b/nss/nss_generic.h
> new file mode 100644
> index 0000000000..fcddf8bf54
> --- /dev/null
> +++ b/nss/nss_generic.h
> @@ -0,0 +1,67 @@
> +/* Type- and query-agnostic NSS functionality.
> +   Copyright (C) 2026 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>
> +#include <stdint.h>
> +
> +enum nss_lookup_type
> +  {
> +#define DEFINE_LOOKUP(name, database, function_1, nscd) 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, nscd) \
> +    HIDDEN_nss_lookup_##name,
> +#include "nss-lookups.def"
> +#undef DEFINE_LOOKUP
> +    nss_lookup_MAX
> +  };
> +
> +/* Copy an NSS struct corresponding to LT into RESULT, 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 *result, 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;
> +
> +/* This array contains a dbtype value (see <nscd/nscd-dbtype.h>) for
> +   each lookup type, or -1 if the lookup has no corresponding database.  */
> +extern int8_t __nscd_database_for_lookup[nss_lookup_MAX] attribute_hidden;
> +
> +#endif /* NSS_GENERIC_H */
> diff --git a/nss/nss_generic_copy.c b/nss/nss_generic_copy.c
> new file mode 100644
> index 0000000000..62d6d40390
> --- /dev/null
> +++ b/nss/nss_generic_copy.c
> @@ -0,0 +1,120 @@
> +/* Copy data referenced in an NSS struct into a fixed-size destination buffer.
> +   Copyright (C) 2026 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 <nss_generic.h>
> +
> +#include <alloc_buffer.h>
> +#include <errno.h>
> +#include <grp.h>
> +#include <pwd.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);
> +}
> +
> +/* Copy *SOURCE into *RESULT, interpreted according to LT.  Use *BUF
> +   to store referenced data for the deep copy.  May mark *BUF as
> +   failed.  */
> +static void
> +__nss_do_copy (enum nss_lookup_type lt, const void *source, void *result,
> +               struct alloc_buffer *buf);
> +
> +/* Implementation of __nss_do_copy for struct group.  Use the same
> +   function signature to help with switch compilation in __nss_do_copy.  */
> +static void
> +__nss_copy_grp (enum nss_lookup_type lt, const struct group *source,
> +                struct group *result, struct alloc_buffer *buf)
> +{
> +  result->gr_gid = source->gr_gid;
> +
> +  if (source->gr_mem == NULL)
> +    result->gr_mem = NULL;
> +  else
> +    {
> +      size_t member_count = 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);
> +}
> +
> +/* Implementation of __nss_do_copy for struct passwd.  */
> +static void
> +__nss_copy_pwd (enum nss_lookup_type lt, const struct passwd *source,
> +                struct passwd *result, struct alloc_buffer *buf)
> +{
> +  result->pw_name = safe_copy_string (buf, source->pw_name);
> +  result->pw_passwd = safe_copy_string (buf, source->pw_passwd);
> +  result->pw_uid = source->pw_uid;
> +  result->pw_gid = source->pw_gid;
> +  result->pw_gecos = safe_copy_string (buf, source->pw_gecos);
> +  result->pw_dir = safe_copy_string (buf, source->pw_dir);
> +  result->pw_shell = safe_copy_string (buf, source->pw_shell);
> +}
> +
> +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);
> +    case nss_lookup_getpwnam:
> +    case nss_lookup_getpwuid:
> +      return __nss_copy_pwd (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);
> +
> +  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..42939f2db9
> --- /dev/null
> +++ b/nss/nss_generic_dup.c
> @@ -0,0 +1,125 @@
> +/* Duplicate NSS data with a single malloc allocation.
> +   Copyright (C) 2026 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 <nss_generic.h>
> +
> +#include <assert.h>
> +#include <alloc_buffer.h>
> +#include <errno.h>
> +#include <grp.h>
> +#include <pwd.h>
> +#include <string.h>
> +
> +/* Return 0 if SOURCE is a null pointer, otherwise the length of the
> +   string including the terminating null byte.  */
> +static size_t
> +safe_strlen_null (const char *source)
> +{
> +  if (source == NULL)
> +    return 0;
> +  else
> +    return strlen (source) + 1;
> +}
> +
> +/* Interpret SOURCE as a pointer to an NSS struct type according to
> +   LT.  Return the size of the buffer space required by the data in
> +   *SOURCE, and write the size of the struct type itself to
> +   *STRUCT_SIZE.  */
> +static size_t
> +__nss_buffer_size (enum nss_lookup_type lt, const void *source,
> +                   size_t *struct_size);
> +
> +/* Variant of __nss_buffer_size for struct group.  */
> +static size_t
> +__nss_group_buffer_size (const struct group *source, size_t *struct_size)
> +{
> +  *struct_size = sizeof (*source);
> +  size_t size = 0;
> +  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;
> +}
> +
> +/* Variant of __nss_buffer_size for struct passwd.  */
> +static size_t
> +__nss_passwd_buffer_size (const struct passwd *source, size_t *struct_size)
> +{
> +  *struct_size = sizeof (*source);
> +  size_t size = 0;
> +  size += safe_strlen_null (source->pw_name);
> +  size += safe_strlen_null (source->pw_passwd);
> +  size += safe_strlen_null (source->pw_gecos);
> +  size += safe_strlen_null (source->pw_dir);
> +  size += safe_strlen_null (source->pw_shell);
> +  return size;
> +}
> +
> +static size_t
> +__nss_buffer_size (enum nss_lookup_type lt, const void *source,
> +                   size_t *struct_size)
> +{
> +  switch (lt)
> +    {
> +    case nss_lookup_getgrgid:
> +    case nss_lookup_getgrnam:
> +      return __nss_group_buffer_size (source, struct_size);
> +    case nss_lookup_getpwnam:
> +    case nss_lookup_getpwuid:
> +      return __nss_passwd_buffer_size (source, struct_size);
> +    }
> +
> +  __builtin_unreachable ();
> +}
> +
> +void *
> +__nss_generic_dup (enum nss_lookup_type lt, const void *source)
> +{
> +  void *result;
> +  char *buf;
> +  char *end;
> +  {
> +    size_t struct_size;
> +    size_t size = __nss_buffer_size (lt, source, &struct_size);
> +    size_t alloc_size = struct_size + size;
> +    result = malloc (alloc_size);
> +    if (result == NULL)
> +      return NULL;
> +    buf = (char *) result + struct_size;
> +    end = (char *) result + alloc_size;
> +  }
> +
> +  int ret __attribute__ ((unused))
> +    = __nss_generic_copy (lt, source, result, buf, end - buf);
> +  /* If this assert fails, *SOURCE was concurrently modified, pointers
> +     in *source aliased and collectively covered more than the address
> +     space (so that size computation overflowed), or malloc did not
> +     return the expected alignment.  */
> +  assert (ret == 0);

Should this be:

if (__nss_generic_copy (lt, source, result, buf, end - buf) != 0)
   abort ();

There are still distros that compile glibc with -DNDEBUG... should we
take a stronger stance on this detection of concurrent modification?

> +
> +  return result;
> +}
> diff --git a/nss/nss_generic_nscd.c b/nss/nss_generic_nscd.c
> new file mode 100644
> index 0000000000..28fc8446ae
> --- /dev/null
> +++ b/nss/nss_generic_nscd.c
> @@ -0,0 +1,29 @@
> +/* Generic malloc-compatible version of nscd get functions.
> +   Copyright (C) 2026 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 <nss_generic.h>
> +
> +#include <nscd/nscd-dbtype.h>
> +
> +int8_t __nscd_database_for_lookup[nss_lookup_MAX] =
> +  {
> +#define DEFINE_LOOKUP(name, dbname, function, nscddb)   \
> +    [nss_lookup_##name] = nscddb,
> +#include <nss-lookups.def>
> +#undef DEFINE_LOOKUP
> +  };
> diff --git a/nss/tst-nss_generic_copy.c b/nss/tst-nss_generic_copy.c
> new file mode 100644
> index 0000000000..fd01d03e6e
> --- /dev/null
> +++ b/nss/tst-nss_generic_copy.c
> @@ -0,0 +1,241 @@
> +/* Test program for the __nss_generic_copy function.
> +   Copyright (C) 2026 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 <nss_generic.h>
> +
> +#include <errno.h>
> +#include <grp.h>
> +#include <pwd.h>
> +#include <string.h>
> +#include <support/check.h>
> +#include <support/check_nss.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);
> +
> +    check_group ("sufficient buffer", &result,
> +                 "name: group-name\n"
> +                 "passwd: password\n"
> +                 "gid: 1000\n"
> +                 "member: user1\n"
> +                 "member: user2\n"
> +                 "member: user3\n");
> +  }
> +
> +  /* 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);
> +
> +    check_group ("NULL group members", &result,
> +                 "name: (null)\n"
> +                 "passwd: (null)\n"
> +                 "gid: 1000\n"
> +                 "gr_mem: (null)\n");
> +  }
> +
> +  /* 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);
> +
> +    check_group ("empty group strings", &result,
> +                 "name: \n"
> +                 "passwd: \n"
> +                 "gid: 1000\n"
> +                 "member: \n");
> +  }
> +}
> +
> +static void
> +test_passwd (enum nss_lookup_type lt)
> +{
> +  struct passwd source_template =
> +    {
> +      .pw_name = (char *) "user-name",
> +      .pw_passwd = (char *) "password",
> +      .pw_uid = 2000,
> +      .pw_gid = 3000,
> +      .pw_gecos = (char *) "User Gecos",
> +      .pw_dir = (char *) "/home/user",
> +      .pw_shell = (char *) "/bin/sh",
> +    };
> +
> +  /* Test with sufficient buffer size.  */
> +  {
> +    struct passwd source = source_template;
> +    char buffer[1024];
> +    struct passwd result;
> +
> +    errno = 23587;
> +    TEST_COMPARE (__nss_generic_copy (lt, &source, &result,
> +                                      buffer, sizeof (buffer)),
> +                  0);
> +    TEST_VERIFY (errno != 0);
> +
> +    check_passwd ("sufficient buffer", &result,
> +                  "name: user-name\n"
> +                  "passwd: password\n"
> +                  "uid: 2000\n"
> +                  "gid: 3000\n"
> +                  "gecos: User Gecos\n"
> +                  "dir: /home/user\n"
> +                  "shell: /bin/sh\n");
> +  }
> +
> +  /* Test with insufficient buffer size.  */
> +  {
> +    struct passwd source = source_template;
> +    char buffer[10];
> +    struct passwd 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 strings.  */
> +  {
> +    struct passwd source = source_template;
> +    source.pw_name = NULL;
> +    source.pw_passwd = NULL;
> +    source.pw_gecos = NULL;
> +    source.pw_dir = NULL;
> +    source.pw_shell = NULL;
> +
> +    char buffer[1024];
> +    struct passwd result;
> +    memset (&result, 0xcc, sizeof (result));
> +
> +    errno = 23587;
> +    TEST_COMPARE (__nss_generic_copy (lt, &source, &result,
> +                                      buffer, sizeof (buffer)),
> +                  0);
> +    TEST_VERIFY (errno != 0);
> +
> +    check_passwd ("NULL passwd fields", &result,
> +                  "name: (null)\n"
> +                  "passwd: (null)\n"
> +                  "uid: 2000\n"
> +                  "gid: 3000\n"
> +                  "gecos: (null)\n"
> +                  "dir: (null)\n"
> +                  "shell: (null)\n");
> +  }
> +
> +  /* Test with empty strings.  */
> +  {
> +    struct passwd source = source_template;
> +    source.pw_name = (char *) "";
> +    source.pw_passwd = (char *) "";
> +    source.pw_gecos = (char *) "";
> +    source.pw_dir = (char *) "";
> +    source.pw_shell = (char *) "";
> +
> +    char buffer[1024];
> +    struct passwd result;
> +
> +    errno = 23587;
> +    TEST_COMPARE (__nss_generic_copy (lt, &source, &result,
> +                                      buffer, sizeof (buffer)),
> +                  0);
> +    TEST_VERIFY (errno != 0);
> +
> +    check_passwd ("empty passwd strings", &result,
> +                  "name: \n"
> +                  "passwd: \n"
> +                  "uid: 2000\n"
> +                  "gid: 3000\n"
> +                  "gecos: \n"
> +                  "dir: \n"
> +                  "shell: \n");
> +  }
> +}
> +
> +static int
> +do_test (void)
> +{
> +  test_group (nss_lookup_getgrgid);
> +  test_group (nss_lookup_getgrnam);
> +  test_passwd (nss_lookup_getpwnam);
> +  test_passwd (nss_lookup_getpwuid);
> +
> +  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..4cf9557fc8
> --- /dev/null
> +++ b/nss/tst-nss_generic_dup.c
> @@ -0,0 +1,231 @@
> +/* Test program for the __nss_generic_dup function.
> +   Copyright (C) 2026 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 <nss_generic.h>
> +
> +#include <grp.h>
> +#include <malloc.h>
> +#include <pwd.h>
> +#include <stdlib.h>
> +#include <support/check.h>
> +#include <support/check_nss.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);
> +
> +    check_group ("normal group data", result,
> +                 "name: group-name\n"
> +                 "passwd: password\n"
> +                 "gid: 1000\n"
> +                 "member: user1\n"
> +                 "member: user2\n"
> +                 "member: long-user-3-name\n");
> +
> +    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);
> +
> +    check_group ("NULL group members", result,
> +                 "name: (null)\n"
> +                 "passwd: (null)\n"
> +                 "gid: 2000\n"
> +                 "gr_mem: (null)\n");
> +
> +    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);
> +
> +    check_group ("empty group strings", result,
> +                 "name: \n"
> +                 "passwd: \n"
> +                 "gid: 3000\n"
> +                 "member: \n");
> +
> +    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 void
> +test_passwd (enum nss_lookup_type lt)
> +{
> +  struct passwd source =
> +    {
> +      .pw_name = (char *) "user-name",
> +      .pw_passwd = (char *) "password",
> +      .pw_uid = 1001,
> +      .pw_gid = 1002,
> +      .pw_gecos = (char *) "gecos-field",
> +      .pw_dir = (char *) "/home/user",
> +      .pw_shell = (char *) "/bin/sh"
> +    };
> +
> +  /* Test with normal passwd data.  */
> +  {
> +    struct passwd *result = __nss_generic_dup (lt, &source);
> +    TEST_VERIFY (result != NULL);
> +
> +    check_passwd ("normal passwd data", result,
> +                  "name: user-name\n"
> +                  "passwd: password\n"
> +                  "uid: 1001\n"
> +                  "gid: 1002\n"
> +                  "gecos: gecos-field\n"
> +                  "dir: /home/user\n"
> +                  "shell: /bin/sh\n");
> +
> +    check_pointer_in_allocation (result, result->pw_name);
> +    check_pointer_in_allocation (result, result->pw_passwd);
> +    check_pointer_in_allocation (result, result->pw_gecos);
> +    check_pointer_in_allocation (result, result->pw_dir);
> +    check_pointer_in_allocation (result, result->pw_shell);
> +
> +    free (result);
> +  }
> +
> +  /* Test with NULL fields.  */
> +  {
> +    struct passwd source_null =
> +      {
> +        .pw_uid = 2001,
> +        .pw_gid = 2002,
> +      };
> +
> +    struct passwd *result = __nss_generic_dup (lt, &source_null);
> +    TEST_VERIFY (result != NULL);
> +
> +    check_passwd ("NULL passwd fields", result,
> +                  "name: (null)\n"
> +                  "passwd: (null)\n"
> +                  "uid: 2001\n"
> +                  "gid: 2002\n"
> +                  "gecos: (null)\n"
> +                  "dir: (null)\n"
> +                  "shell: (null)\n");
> +
> +    free (result);
> +  }
> +
> +  /* Test with empty strings.  */
> +  {
> +    char empty[] = "";
> +    struct passwd source_empty =
> +      {
> +        .pw_name = empty,
> +        .pw_passwd = empty,
> +        .pw_uid = 3001,
> +        .pw_gid = 3002,
> +        .pw_gecos = empty,
> +        .pw_dir = empty,
> +        .pw_shell = empty
> +      };
> +
> +    struct passwd *result = __nss_generic_dup (lt, &source_empty);
> +    TEST_VERIFY (result != NULL);
> +
> +    check_passwd ("empty passwd strings", result,
> +                  "name: \n"
> +                  "passwd: \n"
> +                  "uid: 3001\n"
> +                  "gid: 3002\n"
> +                  "gecos: \n"
> +                  "dir: \n"
> +                  "shell: \n");
> +
> +    check_pointer_in_allocation (result, result->pw_name);
> +    check_pointer_in_allocation (result, result->pw_passwd);
> +    check_pointer_in_allocation (result, result->pw_gecos);
> +    check_pointer_in_allocation (result, result->pw_dir);
> +    check_pointer_in_allocation (result, result->pw_shell);
> +
> +    free (result);
> +  }
> +}
> +
> +static int
> +do_test (void)
> +{
> +  test_group (nss_lookup_getgrgid);
> +  test_group (nss_lookup_getgrnam);
> +  test_passwd (nss_lookup_getpwnam);
> +  test_passwd (nss_lookup_getpwuid);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
  
Florian Weimer March 23, 2026, 4:48 p.m. UTC | #2
* Carlos O'Donell:

> On 3/20/26 4:41 PM, Florian Weimer wrote:
>> So far for struct group and struct passwd only.
>
> LGTM. Nice design. The need for a semantic difference between copy and
> dup wasn't initially apparent to me, but I see that we need it for a
> "caller allocated" buffer versus one we allocate ourselves.
>
> I have one question, which is not a blocker for accepting this change,
> but there is an assert in __nss_generic_dup which might go away when
> built with -NDEBUG (noted by Claude Code v2.1.81 with Sonnet 4.5).
> Do we care about that? Should we make it a runtime abort?

I think removing such run-time aborts is the purpose of building with
-DNDEBUG, at least in glibc's case.

Our asserts are generally intended for production use, not for
development builds only.  People targeting special (typically embedded)
use cases may want to disable them, but it's not the default build
configuration.

Thanks,
Florian
  
Carlos O'Donell March 23, 2026, 5:54 p.m. UTC | #3
On 3/23/26 12:48 PM, Florian Weimer wrote:
> * Carlos O'Donell:
> 
>> On 3/20/26 4:41 PM, Florian Weimer wrote:
>>> So far for struct group and struct passwd only.
>>
>> LGTM. Nice design. The need for a semantic difference between copy and
>> dup wasn't initially apparent to me, but I see that we need it for a
>> "caller allocated" buffer versus one we allocate ourselves.
>>
>> I have one question, which is not a blocker for accepting this change,
>> but there is an assert in __nss_generic_dup which might go away when
>> built with -NDEBUG (noted by Claude Code v2.1.81 with Sonnet 4.5).
>> Do we care about that? Should we make it a runtime abort?
> 
> I think removing such run-time aborts is the purpose of building with
> -DNDEBUG, at least in glibc's case.
> 
> Our asserts are generally intended for production use, not for
> development builds only.  People targeting special (typically embedded)
> use cases may want to disable them, but it's not the default build
> configuration.

If your opinion is that in this particular case we keep the assert then
I'm happy with that. I just wanted to call it out, talk about it, make
sure it wasn't an oversight, and agree to keep the assert.

My review still stands as accepting the patch.
  

Patch

diff --git a/nss/Makefile b/nss/Makefile
index 42d28cf40a..ec68954f3b 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -45,6 +45,9 @@  routines = \
   nss_files_data \
   nss_files_fopen \
   nss_files_functions \
+  nss_generic_copy \
+  nss_generic_dup \
+  nss_generic_nscd \
   nss_hash \
   nss_module \
   nss_parse_line_result \
@@ -302,10 +305,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..80ec36d170
--- /dev/null
+++ b/nss/nss-lookups.def
@@ -0,0 +1,28 @@ 
+/* Lookup type definitions for NSS.
+   Copyright (C) 2026 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", grpdb)
+DEFINE_LOOKUP (getgrnam, group, "getgrnam_r", grpdb)
+DEFINE_LOOKUP (getpwnam, passwd, "getpwnam_r", pwddb)
+DEFINE_LOOKUP (getpwuid, passwd, "getpwuid_r", pwddb)
+
+/*
+   Local Variables:
+    mode:C
+   End:
+ */
diff --git a/nss/nss_generic.h b/nss/nss_generic.h
new file mode 100644
index 0000000000..fcddf8bf54
--- /dev/null
+++ b/nss/nss_generic.h
@@ -0,0 +1,67 @@ 
+/* Type- and query-agnostic NSS functionality.
+   Copyright (C) 2026 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>
+#include <stdint.h>
+
+enum nss_lookup_type
+  {
+#define DEFINE_LOOKUP(name, database, function_1, nscd) 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, nscd) \
+    HIDDEN_nss_lookup_##name,
+#include "nss-lookups.def"
+#undef DEFINE_LOOKUP
+    nss_lookup_MAX
+  };
+
+/* Copy an NSS struct corresponding to LT into RESULT, 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 *result, 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;
+
+/* This array contains a dbtype value (see <nscd/nscd-dbtype.h>) for
+   each lookup type, or -1 if the lookup has no corresponding database.  */
+extern int8_t __nscd_database_for_lookup[nss_lookup_MAX] attribute_hidden;
+
+#endif /* NSS_GENERIC_H */
diff --git a/nss/nss_generic_copy.c b/nss/nss_generic_copy.c
new file mode 100644
index 0000000000..62d6d40390
--- /dev/null
+++ b/nss/nss_generic_copy.c
@@ -0,0 +1,120 @@ 
+/* Copy data referenced in an NSS struct into a fixed-size destination buffer.
+   Copyright (C) 2026 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 <nss_generic.h>
+
+#include <alloc_buffer.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.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);
+}
+
+/* Copy *SOURCE into *RESULT, interpreted according to LT.  Use *BUF
+   to store referenced data for the deep copy.  May mark *BUF as
+   failed.  */
+static void
+__nss_do_copy (enum nss_lookup_type lt, const void *source, void *result,
+               struct alloc_buffer *buf);
+
+/* Implementation of __nss_do_copy for struct group.  Use the same
+   function signature to help with switch compilation in __nss_do_copy.  */
+static void
+__nss_copy_grp (enum nss_lookup_type lt, const struct group *source,
+                struct group *result, struct alloc_buffer *buf)
+{
+  result->gr_gid = source->gr_gid;
+
+  if (source->gr_mem == NULL)
+    result->gr_mem = NULL;
+  else
+    {
+      size_t member_count = 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);
+}
+
+/* Implementation of __nss_do_copy for struct passwd.  */
+static void
+__nss_copy_pwd (enum nss_lookup_type lt, const struct passwd *source,
+                struct passwd *result, struct alloc_buffer *buf)
+{
+  result->pw_name = safe_copy_string (buf, source->pw_name);
+  result->pw_passwd = safe_copy_string (buf, source->pw_passwd);
+  result->pw_uid = source->pw_uid;
+  result->pw_gid = source->pw_gid;
+  result->pw_gecos = safe_copy_string (buf, source->pw_gecos);
+  result->pw_dir = safe_copy_string (buf, source->pw_dir);
+  result->pw_shell = safe_copy_string (buf, source->pw_shell);
+}
+
+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);
+    case nss_lookup_getpwnam:
+    case nss_lookup_getpwuid:
+      return __nss_copy_pwd (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);
+
+  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..42939f2db9
--- /dev/null
+++ b/nss/nss_generic_dup.c
@@ -0,0 +1,125 @@ 
+/* Duplicate NSS data with a single malloc allocation.
+   Copyright (C) 2026 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 <nss_generic.h>
+
+#include <assert.h>
+#include <alloc_buffer.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <string.h>
+
+/* Return 0 if SOURCE is a null pointer, otherwise the length of the
+   string including the terminating null byte.  */
+static size_t
+safe_strlen_null (const char *source)
+{
+  if (source == NULL)
+    return 0;
+  else
+    return strlen (source) + 1;
+}
+
+/* Interpret SOURCE as a pointer to an NSS struct type according to
+   LT.  Return the size of the buffer space required by the data in
+   *SOURCE, and write the size of the struct type itself to
+   *STRUCT_SIZE.  */
+static size_t
+__nss_buffer_size (enum nss_lookup_type lt, const void *source,
+                   size_t *struct_size);
+
+/* Variant of __nss_buffer_size for struct group.  */
+static size_t
+__nss_group_buffer_size (const struct group *source, size_t *struct_size)
+{
+  *struct_size = sizeof (*source);
+  size_t size = 0;
+  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;
+}
+
+/* Variant of __nss_buffer_size for struct passwd.  */
+static size_t
+__nss_passwd_buffer_size (const struct passwd *source, size_t *struct_size)
+{
+  *struct_size = sizeof (*source);
+  size_t size = 0;
+  size += safe_strlen_null (source->pw_name);
+  size += safe_strlen_null (source->pw_passwd);
+  size += safe_strlen_null (source->pw_gecos);
+  size += safe_strlen_null (source->pw_dir);
+  size += safe_strlen_null (source->pw_shell);
+  return size;
+}
+
+static size_t
+__nss_buffer_size (enum nss_lookup_type lt, const void *source,
+                   size_t *struct_size)
+{
+  switch (lt)
+    {
+    case nss_lookup_getgrgid:
+    case nss_lookup_getgrnam:
+      return __nss_group_buffer_size (source, struct_size);
+    case nss_lookup_getpwnam:
+    case nss_lookup_getpwuid:
+      return __nss_passwd_buffer_size (source, struct_size);
+    }
+
+  __builtin_unreachable ();
+}
+
+void *
+__nss_generic_dup (enum nss_lookup_type lt, const void *source)
+{
+  void *result;
+  char *buf;
+  char *end;
+  {
+    size_t struct_size;
+    size_t size = __nss_buffer_size (lt, source, &struct_size);
+    size_t alloc_size = struct_size + size;
+    result = malloc (alloc_size);
+    if (result == NULL)
+      return NULL;
+    buf = (char *) result + struct_size;
+    end = (char *) result + alloc_size;
+  }
+
+  int ret __attribute__ ((unused))
+    = __nss_generic_copy (lt, source, result, buf, end - buf);
+  /* If this assert fails, *SOURCE was concurrently modified, pointers
+     in *source aliased and collectively covered more than the address
+     space (so that size computation overflowed), or malloc did not
+     return the expected alignment.  */
+  assert (ret == 0);
+
+  return result;
+}
diff --git a/nss/nss_generic_nscd.c b/nss/nss_generic_nscd.c
new file mode 100644
index 0000000000..28fc8446ae
--- /dev/null
+++ b/nss/nss_generic_nscd.c
@@ -0,0 +1,29 @@ 
+/* Generic malloc-compatible version of nscd get functions.
+   Copyright (C) 2026 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 <nss_generic.h>
+
+#include <nscd/nscd-dbtype.h>
+
+int8_t __nscd_database_for_lookup[nss_lookup_MAX] =
+  {
+#define DEFINE_LOOKUP(name, dbname, function, nscddb)   \
+    [nss_lookup_##name] = nscddb,
+#include <nss-lookups.def>
+#undef DEFINE_LOOKUP
+  };
diff --git a/nss/tst-nss_generic_copy.c b/nss/tst-nss_generic_copy.c
new file mode 100644
index 0000000000..fd01d03e6e
--- /dev/null
+++ b/nss/tst-nss_generic_copy.c
@@ -0,0 +1,241 @@ 
+/* Test program for the __nss_generic_copy function.
+   Copyright (C) 2026 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 <nss_generic.h>
+
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/check_nss.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);
+
+    check_group ("sufficient buffer", &result,
+                 "name: group-name\n"
+                 "passwd: password\n"
+                 "gid: 1000\n"
+                 "member: user1\n"
+                 "member: user2\n"
+                 "member: user3\n");
+  }
+
+  /* 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);
+
+    check_group ("NULL group members", &result,
+                 "name: (null)\n"
+                 "passwd: (null)\n"
+                 "gid: 1000\n"
+                 "gr_mem: (null)\n");
+  }
+
+  /* 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);
+
+    check_group ("empty group strings", &result,
+                 "name: \n"
+                 "passwd: \n"
+                 "gid: 1000\n"
+                 "member: \n");
+  }
+}
+
+static void
+test_passwd (enum nss_lookup_type lt)
+{
+  struct passwd source_template =
+    {
+      .pw_name = (char *) "user-name",
+      .pw_passwd = (char *) "password",
+      .pw_uid = 2000,
+      .pw_gid = 3000,
+      .pw_gecos = (char *) "User Gecos",
+      .pw_dir = (char *) "/home/user",
+      .pw_shell = (char *) "/bin/sh",
+    };
+
+  /* Test with sufficient buffer size.  */
+  {
+    struct passwd source = source_template;
+    char buffer[1024];
+    struct passwd result;
+
+    errno = 23587;
+    TEST_COMPARE (__nss_generic_copy (lt, &source, &result,
+                                      buffer, sizeof (buffer)),
+                  0);
+    TEST_VERIFY (errno != 0);
+
+    check_passwd ("sufficient buffer", &result,
+                  "name: user-name\n"
+                  "passwd: password\n"
+                  "uid: 2000\n"
+                  "gid: 3000\n"
+                  "gecos: User Gecos\n"
+                  "dir: /home/user\n"
+                  "shell: /bin/sh\n");
+  }
+
+  /* Test with insufficient buffer size.  */
+  {
+    struct passwd source = source_template;
+    char buffer[10];
+    struct passwd 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 strings.  */
+  {
+    struct passwd source = source_template;
+    source.pw_name = NULL;
+    source.pw_passwd = NULL;
+    source.pw_gecos = NULL;
+    source.pw_dir = NULL;
+    source.pw_shell = NULL;
+
+    char buffer[1024];
+    struct passwd result;
+    memset (&result, 0xcc, sizeof (result));
+
+    errno = 23587;
+    TEST_COMPARE (__nss_generic_copy (lt, &source, &result,
+                                      buffer, sizeof (buffer)),
+                  0);
+    TEST_VERIFY (errno != 0);
+
+    check_passwd ("NULL passwd fields", &result,
+                  "name: (null)\n"
+                  "passwd: (null)\n"
+                  "uid: 2000\n"
+                  "gid: 3000\n"
+                  "gecos: (null)\n"
+                  "dir: (null)\n"
+                  "shell: (null)\n");
+  }
+
+  /* Test with empty strings.  */
+  {
+    struct passwd source = source_template;
+    source.pw_name = (char *) "";
+    source.pw_passwd = (char *) "";
+    source.pw_gecos = (char *) "";
+    source.pw_dir = (char *) "";
+    source.pw_shell = (char *) "";
+
+    char buffer[1024];
+    struct passwd result;
+
+    errno = 23587;
+    TEST_COMPARE (__nss_generic_copy (lt, &source, &result,
+                                      buffer, sizeof (buffer)),
+                  0);
+    TEST_VERIFY (errno != 0);
+
+    check_passwd ("empty passwd strings", &result,
+                  "name: \n"
+                  "passwd: \n"
+                  "uid: 2000\n"
+                  "gid: 3000\n"
+                  "gecos: \n"
+                  "dir: \n"
+                  "shell: \n");
+  }
+}
+
+static int
+do_test (void)
+{
+  test_group (nss_lookup_getgrgid);
+  test_group (nss_lookup_getgrnam);
+  test_passwd (nss_lookup_getpwnam);
+  test_passwd (nss_lookup_getpwuid);
+
+  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..4cf9557fc8
--- /dev/null
+++ b/nss/tst-nss_generic_dup.c
@@ -0,0 +1,231 @@ 
+/* Test program for the __nss_generic_dup function.
+   Copyright (C) 2026 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 <nss_generic.h>
+
+#include <grp.h>
+#include <malloc.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/check_nss.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);
+
+    check_group ("normal group data", result,
+                 "name: group-name\n"
+                 "passwd: password\n"
+                 "gid: 1000\n"
+                 "member: user1\n"
+                 "member: user2\n"
+                 "member: long-user-3-name\n");
+
+    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);
+
+    check_group ("NULL group members", result,
+                 "name: (null)\n"
+                 "passwd: (null)\n"
+                 "gid: 2000\n"
+                 "gr_mem: (null)\n");
+
+    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);
+
+    check_group ("empty group strings", result,
+                 "name: \n"
+                 "passwd: \n"
+                 "gid: 3000\n"
+                 "member: \n");
+
+    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 void
+test_passwd (enum nss_lookup_type lt)
+{
+  struct passwd source =
+    {
+      .pw_name = (char *) "user-name",
+      .pw_passwd = (char *) "password",
+      .pw_uid = 1001,
+      .pw_gid = 1002,
+      .pw_gecos = (char *) "gecos-field",
+      .pw_dir = (char *) "/home/user",
+      .pw_shell = (char *) "/bin/sh"
+    };
+
+  /* Test with normal passwd data.  */
+  {
+    struct passwd *result = __nss_generic_dup (lt, &source);
+    TEST_VERIFY (result != NULL);
+
+    check_passwd ("normal passwd data", result,
+                  "name: user-name\n"
+                  "passwd: password\n"
+                  "uid: 1001\n"
+                  "gid: 1002\n"
+                  "gecos: gecos-field\n"
+                  "dir: /home/user\n"
+                  "shell: /bin/sh\n");
+
+    check_pointer_in_allocation (result, result->pw_name);
+    check_pointer_in_allocation (result, result->pw_passwd);
+    check_pointer_in_allocation (result, result->pw_gecos);
+    check_pointer_in_allocation (result, result->pw_dir);
+    check_pointer_in_allocation (result, result->pw_shell);
+
+    free (result);
+  }
+
+  /* Test with NULL fields.  */
+  {
+    struct passwd source_null =
+      {
+        .pw_uid = 2001,
+        .pw_gid = 2002,
+      };
+
+    struct passwd *result = __nss_generic_dup (lt, &source_null);
+    TEST_VERIFY (result != NULL);
+
+    check_passwd ("NULL passwd fields", result,
+                  "name: (null)\n"
+                  "passwd: (null)\n"
+                  "uid: 2001\n"
+                  "gid: 2002\n"
+                  "gecos: (null)\n"
+                  "dir: (null)\n"
+                  "shell: (null)\n");
+
+    free (result);
+  }
+
+  /* Test with empty strings.  */
+  {
+    char empty[] = "";
+    struct passwd source_empty =
+      {
+        .pw_name = empty,
+        .pw_passwd = empty,
+        .pw_uid = 3001,
+        .pw_gid = 3002,
+        .pw_gecos = empty,
+        .pw_dir = empty,
+        .pw_shell = empty
+      };
+
+    struct passwd *result = __nss_generic_dup (lt, &source_empty);
+    TEST_VERIFY (result != NULL);
+
+    check_passwd ("empty passwd strings", result,
+                  "name: \n"
+                  "passwd: \n"
+                  "uid: 3001\n"
+                  "gid: 3002\n"
+                  "gecos: \n"
+                  "dir: \n"
+                  "shell: \n");
+
+    check_pointer_in_allocation (result, result->pw_name);
+    check_pointer_in_allocation (result, result->pw_passwd);
+    check_pointer_in_allocation (result, result->pw_gecos);
+    check_pointer_in_allocation (result, result->pw_dir);
+    check_pointer_in_allocation (result, result->pw_shell);
+
+    free (result);
+  }
+}
+
+static int
+do_test (void)
+{
+  test_group (nss_lookup_getgrgid);
+  test_group (nss_lookup_getgrnam);
+  test_passwd (nss_lookup_getpwnam);
+  test_passwd (nss_lookup_getpwuid);
+
+  return 0;
+}
+
+#include <support/test-driver.c>