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

Carlos O'Donell carlos@redhat.com
Mon Mar 23 16:42:40 GMT 2026


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>


-- 
Cheers,
Carlos.



More information about the Libc-alpha mailing list