[v2,04/23] nss: Add negative lookup test

Message ID 6f3039be2fe4e959a64c69ad177b4a70d539eda1.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-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed

Commit Message

Florian Weimer March 20, 2026, 8:41 p.m. UTC
  ---
 nss/Makefile                  |   3 +
 nss/tst-nss-does-not-exist.cc | 210 ++++++++++++++++++++++++++++++++++
 2 files changed, 213 insertions(+)
 create mode 100644 nss/tst-nss-does-not-exist.cc
  

Comments

Carlos O'Donell March 23, 2026, 3:50 p.m. UTC | #1
On 3/20/26 4:41 PM, Florian Weimer wrote:

LGTM. I like the use of C++ and templates to make the test generic :-)

Reviewed-by: Carlos O'Donell <carlos@redhat.com>

> ---
>   nss/Makefile                  |   3 +
>   nss/tst-nss-does-not-exist.cc | 210 ++++++++++++++++++++++++++++++++++
>   2 files changed, 213 insertions(+)
>   create mode 100644 nss/tst-nss-does-not-exist.cc
> 
> diff --git a/nss/Makefile b/nss/Makefile
> index 1c48bd0876..42d28cf40a 100644
> --- a/nss/Makefile
> +++ b/nss/Makefile
> @@ -324,6 +324,7 @@ tests := \
>     tst-gethnm \
>     tst-getpw \
>     tst-gshadow \
> +  tst-nss-does-not-exist \
>     tst-nss-getpwent \
>     tst-nss-hash \
>     tst-nss-malloc-failure-getlogin_r \
> @@ -537,3 +538,5 @@ LDFLAGS-tst-nss-test4 = -Wl,--disable-new-dtags
>   LDFLAGS-tst-nss-test5 = -Wl,--disable-new-dtags
>   LDFLAGS-tst-nss-test_errno = -Wl,--disable-new-dtags
>   LDFLAGS-tst-nss-test_gai_hv2_canonname = -Wl,--disable-new-dtags
> +
> +LDLIBS-tst-nss-does-not-exist = -lstdc++
> diff --git a/nss/tst-nss-does-not-exist.cc b/nss/tst-nss-does-not-exist.cc
> new file mode 100644
> index 0000000000..72f7c635d8
> --- /dev/null
> +++ b/nss/tst-nss-does-not-exist.cc
> @@ -0,0 +1,210 @@
> +/* Verify that lookup failures have the expected error behavior.
> +   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 case triggers lookup failures for most NSS functions
> +   (both allocated and *_r variants).  It is written in C++ so that
> +   the required types can be inferred from the lookup functions
> +   themselves.  */
> +
> +#include <aliases.h>
> +#include <grp.h>
> +#include <netdb.h>
> +#include <pwd.h>
> +#include <netinet/ether.h>
> +
> +#include <errno.h>
> +#include <nss.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <support/check.h>
> +#include <support/namespace.h>
> +#include <support/test-driver.h>
> +
> +#include <utility>
> +#include <vector>
> +
> +static const char non_existing_name[]
> +  = "this-name-does-not-exist-in-the-system";
> +
> +/* True if *_r lookup functions for type T use an extra h_errno
> +   argument.  */
> +template <class T> constexpr bool uses_h_errno =
> +  std::is_same_v <T, hostent> || std::is_same_v <T, netent>;
> +
> +/* Call a *_r NSS function that produces an NSS lookup of result T
> +   repeatedly until it does not return ERANGE.  The final return value
> +   is written to the ret member, and the pointer to the result
> +   structure to the result member.  The result can be nullptr for a
> +   negative lookup result.  */
> +template <class T>
> +struct erange_wrapper
> +{
> +  int ret;
> +  T *result;
> +
> +  template <class Impl, class... Args>
> +  erange_wrapper (Impl impl, Args&&... args)
> +  {
> +    buffer.resize (32);
> +    result = (T *) -1;
> +    while (true)
> +      {
> +        h_errno = 0;
> +        int herrno = 0;
> +        if constexpr (uses_h_errno <T>)
> +          ret = impl (std::forward<Args>(args)...,
> +                      &storage, buffer.data (), buffer.size (), &result,
> +                      &herrno);
> +        else
> +          {
> +            /* No h_errno pointer argument.  Pretend that failures
> +               are always due to NETDB_INTERNAL.  */
> +            ret = impl (std::forward<Args>(args)...,
> +                        &storage, buffer.data (), buffer.size (), &result);
> +            herrno = NETDB_INTERNAL;
> +          }
> +        if (herrno == NETDB_INTERNAL && ret == ERANGE)
> +          {
> +            buffer.resize (buffer.size () * 2);
> +            TEST_VERIFY (buffer.size () > 0);
> +          }
> +        else
> +          break;
> +      }
> +  }
> +
> +  /* Verify that ret and result denote a negative lookup result.  */
> +  void expected_negative ()
> +  {
> +    TEST_COMPARE (ret, 0);
> +    TEST_VERIFY (result == nullptr);
> +  }
> +
> +private:
> +  T storage;
> +  std::vector<char> buffer;
> +};
> +
> +/* Verify that looking up non_existing_name fails for the non-_r and _r
> +   function variants.  */
> +template <class Lookup, class LookupR, class... Args>
> +static void
> +check_missing_name (Lookup lookup, LookupR lookup_r, Args&&... args)
> +{
> +  verbose_printf ("info: name lookup: %s\n",
> +                  strchr (__PRETTY_FUNCTION__, '['));
> +  errno = 0;
> +  auto r = lookup (non_existing_name, std::forward<Args>(args)...);
> +  TEST_VERIFY (r == nullptr);
> +  TEST_COMPARE (errno, 0);
> +  using nss_type = typename std::remove_reference <decltype (*r)>::type;
> +  erange_wrapper <nss_type> (lookup_r, non_existing_name,
> +                             std::forward<Args>(args)...).expected_negative ();
> +}
> +
> +/* Find an ID that results in a lookup failure using the non-_r function,
> +   and then verify that it also fails with the _r function.  ID_MEMBER
> +   is the pointer to the struct member that contains the looked-up ID.  */
> +template <class Lookup, class LookupR, class Member, class... Args>
> +static void
> +find_missing_id (const char *type_name, Lookup lookup, LookupR lookup_r,
> +                 Member id_member, Args&&... args)
> +{
> +  verbose_printf ("info: ID lookup for %s: %s\n",
> +                  type_name, strchr (__PRETTY_FUNCTION__, '['));
> +  using nss_type = typename std::remove_reference
> +    <decltype (*lookup (0, std::forward<Args>(args)...))>::type;
> +  using id_type = decltype(nss_type{}.*id_member);
> +  for (id_type i = 0; i < 65536; ++i)
> +    {
> +      verbose_printf ("info:   trying %lld\n", (long long int) i);
> +      errno = 0;
> +      nss_type *r = lookup (i, std::forward<Args>(args)...);
> +      if (r == nullptr)
> +        {
> +          TEST_COMPARE (errno, 0);
> +          printf ("info: first missing %s: %lld\n", type_name,
> +                  (long long int) i);
> +
> +          erange_wrapper <nss_type> (lookup_r, i, std::forward<Args>(args)...)
> +            .expected_negative ();
> +          return;
> +        }
> +      TEST_COMPARE (r->*id_member, i);
> +    }
> +  printf ("warning: could not find an undefined ID for %s\n", type_name);
> +}
> +
> +static void
> +checks (void *closure)
> +{
> +  check_missing_name (getpwnam, getpwnam_r);
> +  find_missing_id ("passwd", getpwuid, getpwuid_r, &passwd::pw_uid);
> +
> +  check_missing_name (getgrnam, getgrnam_r);
> +  find_missing_id ("group", getgrgid, getgrgid_r, &group::gr_gid);
> +
> +  check_missing_name (getprotobyname, getprotobyname_r);
> +  find_missing_id ("protoent", getprotobynumber, getprotobynumber_r,
> +                   &protoent::p_proto);
> +
> +  check_missing_name (getservbyname, getservbyname_r, "tcp");
> +  find_missing_id ("servent", getservbyport, getservbyport_r,
> +                   &servent::s_port, "tcp");
> +
> +  check_missing_name (getaliasbyname, getaliasbyname_r);
> +
> +  check_missing_name (getrpcbyname, getrpcbyname_r);
> +  find_missing_id ("rpcent", getrpcbynumber, getrpcbynumber_r,
> +                   &rpcent::r_number);
> +
> +  check_missing_name (gethostbyname, gethostbyname_r);
> +  check_missing_name (gethostbyname2, gethostbyname2_r, AF_INET);
> +
> +  check_missing_name (getnetbyname, getnetbyname_r);
> +
> +  /* Exceptional case: no buffer argument, no ERANGE protocol.  */
> +  {
> +    errno = 0;
> +    TEST_VERIFY (ether_aton (non_existing_name) == nullptr);
> +    TEST_COMPARE (errno, 0);
> +    struct ether_addr addr;
> +    TEST_VERIFY (ether_aton_r (non_existing_name, &addr) == nullptr);
> +    TEST_COMPARE (errno, 0);
> +  }
> +}
> +
> +static int
> +do_test (void)
> +{
> +  /* First test the system default.  */
> +  puts ("info: testing system default");
> +  support_isolate_in_subprocess (checks, nullptr);
> +
> +  /* Then test the files module specifically.  */
> +  puts ("info: testing files module");
> +#define DEFINE_DATABASE(db) __nss_configure_lookup (#db, "files");
> +#include "databases.def"
> +#undef DEFINE_DATABASE
> +  support_isolate_in_subprocess (checks, nullptr);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
  

Patch

diff --git a/nss/Makefile b/nss/Makefile
index 1c48bd0876..42d28cf40a 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -324,6 +324,7 @@  tests := \
   tst-gethnm \
   tst-getpw \
   tst-gshadow \
+  tst-nss-does-not-exist \
   tst-nss-getpwent \
   tst-nss-hash \
   tst-nss-malloc-failure-getlogin_r \
@@ -537,3 +538,5 @@  LDFLAGS-tst-nss-test4 = -Wl,--disable-new-dtags
 LDFLAGS-tst-nss-test5 = -Wl,--disable-new-dtags
 LDFLAGS-tst-nss-test_errno = -Wl,--disable-new-dtags
 LDFLAGS-tst-nss-test_gai_hv2_canonname = -Wl,--disable-new-dtags
+
+LDLIBS-tst-nss-does-not-exist = -lstdc++
diff --git a/nss/tst-nss-does-not-exist.cc b/nss/tst-nss-does-not-exist.cc
new file mode 100644
index 0000000000..72f7c635d8
--- /dev/null
+++ b/nss/tst-nss-does-not-exist.cc
@@ -0,0 +1,210 @@ 
+/* Verify that lookup failures have the expected error behavior.
+   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 case triggers lookup failures for most NSS functions
+   (both allocated and *_r variants).  It is written in C++ so that
+   the required types can be inferred from the lookup functions
+   themselves.  */
+
+#include <aliases.h>
+#include <grp.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <netinet/ether.h>
+
+#include <errno.h>
+#include <nss.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/test-driver.h>
+
+#include <utility>
+#include <vector>
+
+static const char non_existing_name[]
+  = "this-name-does-not-exist-in-the-system";
+
+/* True if *_r lookup functions for type T use an extra h_errno
+   argument.  */
+template <class T> constexpr bool uses_h_errno =
+  std::is_same_v <T, hostent> || std::is_same_v <T, netent>;
+
+/* Call a *_r NSS function that produces an NSS lookup of result T
+   repeatedly until it does not return ERANGE.  The final return value
+   is written to the ret member, and the pointer to the result
+   structure to the result member.  The result can be nullptr for a
+   negative lookup result.  */
+template <class T>
+struct erange_wrapper
+{
+  int ret;
+  T *result;
+
+  template <class Impl, class... Args>
+  erange_wrapper (Impl impl, Args&&... args)
+  {
+    buffer.resize (32);
+    result = (T *) -1;
+    while (true)
+      {
+        h_errno = 0;
+        int herrno = 0;
+        if constexpr (uses_h_errno <T>)
+          ret = impl (std::forward<Args>(args)...,
+                      &storage, buffer.data (), buffer.size (), &result,
+                      &herrno);
+        else
+          {
+            /* No h_errno pointer argument.  Pretend that failures
+               are always due to NETDB_INTERNAL.  */
+            ret = impl (std::forward<Args>(args)...,
+                        &storage, buffer.data (), buffer.size (), &result);
+            herrno = NETDB_INTERNAL;
+          }
+        if (herrno == NETDB_INTERNAL && ret == ERANGE)
+          {
+            buffer.resize (buffer.size () * 2);
+            TEST_VERIFY (buffer.size () > 0);
+          }
+        else
+          break;
+      }
+  }
+
+  /* Verify that ret and result denote a negative lookup result.  */
+  void expected_negative ()
+  {
+    TEST_COMPARE (ret, 0);
+    TEST_VERIFY (result == nullptr);
+  }
+
+private:
+  T storage;
+  std::vector<char> buffer;
+};
+
+/* Verify that looking up non_existing_name fails for the non-_r and _r
+   function variants.  */
+template <class Lookup, class LookupR, class... Args>
+static void
+check_missing_name (Lookup lookup, LookupR lookup_r, Args&&... args)
+{
+  verbose_printf ("info: name lookup: %s\n",
+                  strchr (__PRETTY_FUNCTION__, '['));
+  errno = 0;
+  auto r = lookup (non_existing_name, std::forward<Args>(args)...);
+  TEST_VERIFY (r == nullptr);
+  TEST_COMPARE (errno, 0);
+  using nss_type = typename std::remove_reference <decltype (*r)>::type;
+  erange_wrapper <nss_type> (lookup_r, non_existing_name,
+                             std::forward<Args>(args)...).expected_negative ();
+}
+
+/* Find an ID that results in a lookup failure using the non-_r function,
+   and then verify that it also fails with the _r function.  ID_MEMBER
+   is the pointer to the struct member that contains the looked-up ID.  */
+template <class Lookup, class LookupR, class Member, class... Args>
+static void
+find_missing_id (const char *type_name, Lookup lookup, LookupR lookup_r,
+                 Member id_member, Args&&... args)
+{
+  verbose_printf ("info: ID lookup for %s: %s\n",
+                  type_name, strchr (__PRETTY_FUNCTION__, '['));
+  using nss_type = typename std::remove_reference
+    <decltype (*lookup (0, std::forward<Args>(args)...))>::type;
+  using id_type = decltype(nss_type{}.*id_member);
+  for (id_type i = 0; i < 65536; ++i)
+    {
+      verbose_printf ("info:   trying %lld\n", (long long int) i);
+      errno = 0;
+      nss_type *r = lookup (i, std::forward<Args>(args)...);
+      if (r == nullptr)
+        {
+          TEST_COMPARE (errno, 0);
+          printf ("info: first missing %s: %lld\n", type_name,
+                  (long long int) i);
+
+          erange_wrapper <nss_type> (lookup_r, i, std::forward<Args>(args)...)
+            .expected_negative ();
+          return;
+        }
+      TEST_COMPARE (r->*id_member, i);
+    }
+  printf ("warning: could not find an undefined ID for %s\n", type_name);
+}
+
+static void
+checks (void *closure)
+{
+  check_missing_name (getpwnam, getpwnam_r);
+  find_missing_id ("passwd", getpwuid, getpwuid_r, &passwd::pw_uid);
+
+  check_missing_name (getgrnam, getgrnam_r);
+  find_missing_id ("group", getgrgid, getgrgid_r, &group::gr_gid);
+
+  check_missing_name (getprotobyname, getprotobyname_r);
+  find_missing_id ("protoent", getprotobynumber, getprotobynumber_r,
+                   &protoent::p_proto);
+
+  check_missing_name (getservbyname, getservbyname_r, "tcp");
+  find_missing_id ("servent", getservbyport, getservbyport_r,
+                   &servent::s_port, "tcp");
+
+  check_missing_name (getaliasbyname, getaliasbyname_r);
+
+  check_missing_name (getrpcbyname, getrpcbyname_r);
+  find_missing_id ("rpcent", getrpcbynumber, getrpcbynumber_r,
+                   &rpcent::r_number);
+
+  check_missing_name (gethostbyname, gethostbyname_r);
+  check_missing_name (gethostbyname2, gethostbyname2_r, AF_INET);
+
+  check_missing_name (getnetbyname, getnetbyname_r);
+
+  /* Exceptional case: no buffer argument, no ERANGE protocol.  */
+  {
+    errno = 0;
+    TEST_VERIFY (ether_aton (non_existing_name) == nullptr);
+    TEST_COMPARE (errno, 0);
+    struct ether_addr addr;
+    TEST_VERIFY (ether_aton_r (non_existing_name, &addr) == nullptr);
+    TEST_COMPARE (errno, 0);
+  }
+}
+
+static int
+do_test (void)
+{
+  /* First test the system default.  */
+  puts ("info: testing system default");
+  support_isolate_in_subprocess (checks, nullptr);
+
+  /* Then test the files module specifically.  */
+  puts ("info: testing files module");
+#define DEFINE_DATABASE(db) __nss_configure_lookup (#db, "files");
+#include "databases.def"
+#undef DEFINE_DATABASE
+  support_isolate_in_subprocess (checks, nullptr);
+
+  return 0;
+}
+
+#include <support/test-driver.c>