[PATCH v2 04/23] nss: Add negative lookup test
Carlos O'Donell
carlos@redhat.com
Mon Mar 23 15:50:56 GMT 2026
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>
--
Cheers,
Carlos.
More information about the Libc-alpha
mailing list