[PATCH 2/7] nss: Add negative lookup test
Florian Weimer
fweimer@redhat.com
Mon Nov 24 19:47:47 GMT 2025
---
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 0586381877..07556a5958 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-test1 \
@@ -536,3 +537,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..0c84144d0c
--- /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) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+/* This test 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>
--
2.52.0
More information about the Libc-alpha
mailing list