[v2,04/23] nss: Add negative lookup test
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
---
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
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>
@@ -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++
new file mode 100644
@@ -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>