[v2,07/23] nss: Add __nss_generic_copy and __nss_generic_dup functions
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
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
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>
* 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
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.
@@ -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 \
new file mode 100644
@@ -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:
+ */
new file mode 100644
@@ -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 */
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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
+ };
new file mode 100644
@@ -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>
new file mode 100644
@@ -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>