diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index ad2d3ad84b..2cec3bdc16 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -198,11 +198,6 @@ All tools: prefixed with `:` in which case the kernel command line option takes precedence, if it is specified as well. -installed systemd tests: - -* `$SYSTEMD_TEST_DATA` — override the location of test data. This is useful if - a test executable is moved to an arbitrary location. - `nss-systemd`: * `$SYSTEMD_NSS_BYPASS_SYNTHETIC=1` — if set, `nss-systemd` won't synthesize @@ -302,6 +297,14 @@ installed systemd tests: * `$SYSTEMD_SYSVRCND_PATH` — Controls where `systemd-sysv-generator` looks for SysV init script runlevel link farms. +systemd tests: + +* `$SYSTEMD_TEST_DATA` — override the location of test data. This is useful if + a test executable is moved to an arbitrary location. + +* `$SYSTEMD_TEST_NSS_BUFSIZE` — size of scratch buffers for "reentrant" + functions exported by the nss modules. + fuzzers: * `$SYSTEMD_FUZZ_OUTPUT` — A boolean that specifies whether to write output to diff --git a/src/basic/nss-util.h b/src/basic/nss-util.h index dfc0d3fb20..3c59dcc03c 100644 --- a/src/basic/nss-util.h +++ b/src/basic/nss-util.h @@ -213,3 +213,25 @@ typedef enum nss_status (*_nss_gethostbyaddr_r_t)( struct hostent *host, char *buffer, size_t buflen, int *errnop, int *h_errnop); + +typedef enum nss_status (*_nss_getpwnam_r_t)( + const char *name, + struct passwd *pwd, + char *buffer, size_t buflen, + int *errnop); +typedef enum nss_status (*_nss_getpwuid_r_t)( + uid_t uid, + struct passwd *pwd, + char *buffer, size_t buflen, + int *errnop); + +typedef enum nss_status (*_nss_getgrnam_r_t)( + const char *name, + struct group *gr, + char *buffer, size_t buflen, + int *errnop); +typedef enum nss_status (*_nss_getgrgid_r_t)( + gid_t gid, + struct group *gr, + char *buffer, size_t buflen, + int *errnop); diff --git a/src/test/meson.build b/src/test/meson.build index ff40a8d10d..0488baba82 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -540,7 +540,16 @@ tests += [ [['src/test/test-gcrypt-util.c'], [], [], [], 'HAVE_GCRYPT'], - [['src/test/test-nss.c'], + [['src/test/test-nss-hosts.c', + 'src/test/nss-test-util.c', + 'src/test/nss-test-util.h'], + [], + [libdl], + [], 'ENABLE_NSS', 'manual'], + + [['src/test/test-nss-users.c', + 'src/test/nss-test-util.c', + 'src/test/nss-test-util.h'], [], [libdl], [], 'ENABLE_NSS', 'manual'], diff --git a/src/test/nss-test-util.c b/src/test/nss-test-util.c new file mode 100644 index 0000000000..fc1d724a2f --- /dev/null +++ b/src/test/nss-test-util.c @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "nss-test-util.h" +#include "string-util.h" + +const char* nss_status_to_string(enum nss_status status, char *buf, size_t buf_len) { + switch (status) { + case NSS_STATUS_TRYAGAIN: + return "NSS_STATUS_TRYAGAIN"; + case NSS_STATUS_UNAVAIL: + return "NSS_STATUS_UNAVAIL"; + case NSS_STATUS_NOTFOUND: + return "NSS_STATUS_NOTFOUND"; + case NSS_STATUS_SUCCESS: + return "NSS_STATUS_SUCCESS"; + case NSS_STATUS_RETURN: + return "NSS_STATUS_RETURN"; + default: + snprintf(buf, buf_len, "%i", status); + return buf; + } +}; + +void* nss_open_handle(const char *dir, const char *module, int flags) { + const char *path = NULL; + void *handle; + + if (dir) + path = strjoina(dir, "/libnss_", module, ".so.2"); + if (!path || access(path, F_OK) < 0) + path = strjoina("libnss_", module, ".so.2"); + + log_debug("Using %s", path); + handle = dlopen(path, flags); + if (!handle) + log_error("Failed to load module %s: %s", module, dlerror()); + return handle; +} diff --git a/src/test/nss-test-util.h b/src/test/nss-test-util.h new file mode 100644 index 0000000000..f081e6467e --- /dev/null +++ b/src/test/nss-test-util.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +const char* nss_status_to_string(enum nss_status status, char *buf, size_t buf_len); +void* nss_open_handle(const char *dir, const char *module, int flags); diff --git a/src/test/test-nss.c b/src/test/test-nss-hosts.c similarity index 87% rename from src/test/test-nss.c rename to src/test/test-nss-hosts.c index 2e9414d16d..e9bc6ecce6 100644 --- a/src/test/test-nss.c +++ b/src/test/test-nss-hosts.c @@ -1,12 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include #include "af-list.h" #include "alloc-util.h" +#include "dlfcn-util.h" #include "errno-list.h" #include "format-util.h" #include "hexdecoct.h" @@ -15,30 +15,16 @@ #include "local-addresses.h" #include "log.h" #include "main-func.h" +#include "nss-test-util.h" #include "nss-util.h" +#include "parse-util.h" #include "path-util.h" #include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "tests.h" -static const char* nss_status_to_string(enum nss_status status, char *buf, size_t buf_len) { - switch (status) { - case NSS_STATUS_TRYAGAIN: - return "NSS_STATUS_TRYAGAIN"; - case NSS_STATUS_UNAVAIL: - return "NSS_STATUS_UNAVAIL"; - case NSS_STATUS_NOTFOUND: - return "NSS_STATUS_NOTFOUND"; - case NSS_STATUS_SUCCESS: - return "NSS_STATUS_SUCCESS"; - case NSS_STATUS_RETURN: - return "NSS_STATUS_RETURN"; - default: - snprintf(buf, buf_len, "%i", status); - return buf; - } -}; +static size_t arg_bufsize = 1024; static const char* af_to_string(int family, char *buf, size_t buf_len) { const char *name; @@ -54,22 +40,6 @@ static const char* af_to_string(int family, char *buf, size_t buf_len) { return buf; } -static void* open_handle(const char *dir, const char *module, int flags) { - const char *path = NULL; - void *handle; - - if (dir) - path = strjoina(dir, "/libnss_", module, ".so.2"); - if (!path || access(path, F_OK) < 0) - path = strjoina("libnss_", module, ".so.2"); - - log_debug("Using %s", path); - handle = dlopen(path, flags); - if (!handle) - log_error("Failed to load module %s: %s", module, dlerror()); - return handle; -} - static int print_gaih_addrtuples(const struct gaih_addrtuple *tuples) { int n = 0; @@ -132,7 +102,7 @@ static void print_struct_hostent(struct hostent *host, const char *canon) { static void test_gethostbyname4_r(void *handle, const char *module, const char *name) { const char *fname; _nss_gethostbyname4_r_t f; - char buffer[2000]; + char buffer[arg_bufsize]; struct gaih_addrtuple *pat = NULL; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ int32_t ttl = INT32_MAX; /* nss-dns wants to return the lowest ttl, @@ -184,7 +154,7 @@ static void test_gethostbyname4_r(void *handle, const char *module, const char * static void test_gethostbyname3_r(void *handle, const char *module, const char *name, int af) { const char *fname; _nss_gethostbyname3_r_t f; - char buffer[2000]; + char buffer[arg_bufsize]; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ int32_t ttl = INT32_MAX; /* nss-dns wants to return the lowest ttl, and will access this variable through *ttlp, @@ -219,7 +189,7 @@ static void test_gethostbyname3_r(void *handle, const char *module, const char * static void test_gethostbyname2_r(void *handle, const char *module, const char *name, int af) { const char *fname; _nss_gethostbyname2_r_t f; - char buffer[2000]; + char buffer[arg_bufsize]; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ enum nss_status status; char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; @@ -247,7 +217,7 @@ static void test_gethostbyname2_r(void *handle, const char *module, const char * static void test_gethostbyname_r(void *handle, const char *module, const char *name) { const char *fname; _nss_gethostbyname_r_t f; - char buffer[2000]; + char buffer[arg_bufsize]; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ enum nss_status status; char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; @@ -278,7 +248,7 @@ static void test_gethostbyaddr2_r(void *handle, const char *fname; _nss_gethostbyaddr2_r_t f; - char buffer[2000]; + char buffer[arg_bufsize]; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ enum nss_status status; char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; @@ -316,7 +286,7 @@ static void test_gethostbyaddr_r(void *handle, const char *fname; _nss_gethostbyaddr_r_t f; - char buffer[2000]; + char buffer[arg_bufsize]; int errno1 = 999, errno2 = 999; /* nss-dns doesn't set those */ enum nss_status status; char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; @@ -392,8 +362,7 @@ static int make_addresses(struct local_address **addresses) { log_info_errno(n, "Failed to query local addresses: %m"); n_alloc = n; /* we _can_ do that */ - if (!GREEDY_REALLOC(addrs, n_alloc, n + 3)) - return log_oom(); + assert_se(GREEDY_REALLOC(addrs, n_alloc, n + 3)); addrs[n++] = (struct local_address) { .family = AF_INET, .address.in = { htobe32(0x7F000001) } }; @@ -409,15 +378,14 @@ static int test_one_module(const char *dir, char **names, struct local_address *addresses, int n_addresses) { - void *handle; - char **name; log_info("======== %s ========", module); - handle = open_handle(dir, module, RTLD_LAZY|RTLD_NODELETE); + _cleanup_(dlclosep) void *handle = nss_open_handle(dir, module, RTLD_LAZY|RTLD_NODELETE); if (!handle) return -EINVAL; + char **name; STRV_FOREACH(name, names) test_byname(handle, module, *name); @@ -428,7 +396,6 @@ static int test_one_module(const char *dir, addresses[i].family); log_info(" "); - dlclose(handle); return 0; } @@ -437,10 +404,18 @@ static int parse_argv(int argc, char **argv, char ***the_names, struct local_address **the_addresses, int *n_addresses) { - int r, n = 0; _cleanup_strv_free_ char **modules = NULL, **names = NULL; _cleanup_free_ struct local_address *addrs = NULL; size_t n_allocated = 0; + const char *p; + int r, n = 0; + + p = getenv("SYSTEMD_TEST_NSS_BUFSIZE"); + if (p) { + r = safe_atozu(p, &arg_bufsize); + if (r < 0) + return log_error_errno(r, "Failed to parse $SYSTEMD_TEST_NSS_BUFSIZE"); + } if (argc > 1) modules = strv_new(argv[1]); @@ -456,8 +431,7 @@ static int parse_argv(int argc, char **argv, "mymachines", #endif "dns"); - if (!modules) - return -ENOMEM; + assert_se(modules); if (argc > 2) { char **name; @@ -472,8 +446,7 @@ static int parse_argv(int argc, char **argv, if (r < 0) return r; } else { - if (!GREEDY_REALLOC0(addrs, n_allocated, n + 1)) - return -ENOMEM; + assert_se(GREEDY_REALLOC0(addrs, n_allocated, n + 1)); addrs[n++] = (struct local_address) { .family = family, .address = address }; @@ -481,26 +454,18 @@ static int parse_argv(int argc, char **argv, } } else { _cleanup_free_ char *hostname; + assert_se(hostname = gethostname_malloc()); - hostname = gethostname_malloc(); - if (!hostname) - return -ENOMEM; - - names = strv_new("localhost", "_gateway", "foo_no_such_host", hostname); - if (!names) - return -ENOMEM; + assert_se(names = strv_new("localhost", "_gateway", "foo_no_such_host", hostname)); n = make_addresses(&addrs); - if (n < 0) - return n; + assert_se(n >= 0); } - *the_modules = modules; - *the_names = names; - modules = names = NULL; - *the_addresses = addrs; + *the_modules = TAKE_PTR(modules); + *the_names = TAKE_PTR(names); + *the_addresses = TAKE_PTR(addrs); *n_addresses = n; - addrs = NULL; return 0; } @@ -515,14 +480,10 @@ static int run(int argc, char **argv) { test_setup_logging(LOG_INFO); r = parse_argv(argc, argv, &modules, &names, &addresses, &n_addresses); - if (r < 0) { - log_error_errno(r, "Failed to parse arguments: %m"); - return EXIT_FAILURE; - } + if (r < 0) + return log_error_errno(r, "Failed to parse arguments: %m"); - dir = dirname_malloc(argv[0]); - if (!dir) - return log_oom(); + assert_se(path_extract_directory(argv[0], &dir) >= 0); STRV_FOREACH(module, modules) { r = test_one_module(dir, *module, names, addresses, n_addresses); diff --git a/src/test/test-nss-users.c b/src/test/test-nss-users.c new file mode 100644 index 0000000000..c415c0ca3b --- /dev/null +++ b/src/test/test-nss-users.c @@ -0,0 +1,258 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dlfcn-util.h" +#include "errno-list.h" +#include "format-util.h" +#include "log.h" +#include "main-func.h" +#include "nss-test-util.h" +#include "nss-util.h" +#include "path-util.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "user-util.h" + +static size_t arg_bufsize = 1024; + +static void print_struct_passwd(const struct passwd *pwd) { + log_info(" \"%s\" / "UID_FMT":"GID_FMT, + pwd->pw_name, pwd->pw_uid, pwd->pw_gid); + log_info(" passwd=\"%s\"", pwd->pw_passwd); + log_info(" gecos=\"%s\"", pwd->pw_gecos); + log_info(" dir=\"%s\"", pwd->pw_dir); + log_info(" shell=\"%s\"", pwd->pw_shell); +} + +static void print_struct_group(const struct group *gr) { + _cleanup_free_ char *members = NULL; + + log_info(" \"%s\" / "GID_FMT, + gr->gr_name, gr->gr_gid); + log_info(" passwd=\"%s\"", gr->gr_passwd); + + assert_se(members = strv_join(gr->gr_mem, ", ")); + // FIXME: use shell_maybe_quote(SHELL_ESCAPE_EMPTY) when it becomes available + log_info(" members=%s", members); +} + +static void test_getpwnam_r(void *handle, const char *module, const char *name) { + const char *fname; + _nss_getpwnam_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct passwd pwd; + + fname = strjoina("_nss_", module, "_getpwnam_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(name, &pwd, buffer, sizeof buffer, &errno1); + log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s", + fname, name, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---"); + if (status == NSS_STATUS_SUCCESS) + print_struct_passwd(&pwd); +} + +static void test_getgrnam_r(void *handle, const char *module, const char *name) { + const char *fname; + _nss_getgrnam_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct group gr; + + fname = strjoina("_nss_", module, "_getgrnam_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(name, &gr, buffer, sizeof buffer, &errno1); + log_info("%s(\"%s\") → status=%s%-20serrno=%d/%s", + fname, name, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---"); + if (status == NSS_STATUS_SUCCESS) + print_struct_group(&gr); +} + +static void test_getpwuid_r(void *handle, const char *module, uid_t uid) { + const char *fname; + _nss_getpwuid_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct passwd pwd; + + fname = strjoina("_nss_", module, "_getpwuid_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(uid, &pwd, buffer, sizeof buffer, &errno1); + log_info("%s("UID_FMT") → status=%s%-20serrno=%d/%s", + fname, uid, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---"); + if (status == NSS_STATUS_SUCCESS) + print_struct_passwd(&pwd); +} + +static void test_getgrgid_r(void *handle, const char *module, gid_t gid) { + const char *fname; + _nss_getgrgid_r_t f; + char buffer[arg_bufsize]; + int errno1 = 999; /* nss-dns doesn't set those */ + enum nss_status status; + char pretty_status[DECIMAL_STR_MAX(enum nss_status)]; + struct group gr; + + fname = strjoina("_nss_", module, "_getgrgid_r"); + f = dlsym(handle, fname); + log_debug("dlsym(0x%p, %s) → 0x%p", handle, fname, f); + if (!f) { + log_info("%s not defined", fname); + return; + } + + status = f(gid, &gr, buffer, sizeof buffer, &errno1); + log_info("%s("GID_FMT") → status=%s%-20serrno=%d/%s", + fname, gid, + nss_status_to_string(status, pretty_status, sizeof pretty_status), "\n", + errno1, errno_to_name(errno1) ?: "---"); + if (status == NSS_STATUS_SUCCESS) + print_struct_group(&gr); +} + +static void test_byname(void *handle, const char *module, const char *name) { + test_getpwnam_r(handle, module, name); + test_getgrnam_r(handle, module, name); + puts(""); +} + +static void test_byuid(void *handle, const char *module, uid_t uid) { + test_getpwuid_r(handle, module, uid); + test_getgrgid_r(handle, module, uid); + puts(""); +} + +static int test_one_module(const char *dir, + const char *module, + char **names) { + + log_info("======== %s ========", module); + + _cleanup_(dlclosep) void *handle = nss_open_handle(dir, module, RTLD_LAZY|RTLD_NODELETE); + if (!handle) + return -EINVAL; + + char **name; + STRV_FOREACH(name, names) + test_byname(handle, module, *name); + + STRV_FOREACH(name, names) { + uid_t uid; + + assert_cc(sizeof(uid_t) == sizeof(uint32_t)); + /* We use safe_atou32 because we don't want to refuse invalid uids. */ + if (safe_atou32(*name, &uid) < 0) + continue; + + test_byuid(handle, module, uid); + } + + log_info(" "); + return 0; +} + +static int parse_argv(int argc, char **argv, + char ***the_modules, + char ***the_names) { + + _cleanup_strv_free_ char **modules = NULL, **names = NULL; + const char *p; + int r; + + p = getenv("SYSTEMD_TEST_NSS_BUFSIZE"); + if (p) { + r = safe_atozu(p, &arg_bufsize); + if (r < 0) + return log_error_errno(r, "Failed to parse $SYSTEMD_TEST_NSS_BUFSIZE"); + } + + if (argc > 1) + modules = strv_new(argv[1]); + else + modules = strv_new( +#if ENABLE_NSS_SYSTEMD + "systemd", +#endif +#if ENABLE_NSS_MYMACHINES + "mymachines", +#endif + "files"); + assert_se(modules); + + if (argc > 2) + names = strv_copy(strv_skip(argv, 2)); + else + names = strv_new("root", + NOBODY_USER_NAME, + "foo_no_such_user", + "0", + "65534"); + assert_se(names); + + *the_modules = TAKE_PTR(modules); + *the_names = TAKE_PTR(names); + return 0; +} + +static int run(int argc, char **argv) { + _cleanup_free_ char *dir = NULL; + _cleanup_strv_free_ char **modules = NULL, **names = NULL; + char **module; + int r; + + test_setup_logging(LOG_INFO); + + r = parse_argv(argc, argv, &modules, &names); + if (r < 0) + return log_error_errno(r, "Failed to parse arguments: %m"); + + assert_se(path_extract_directory(argv[0], &dir) >= 0); + + STRV_FOREACH(module, modules) { + r = test_one_module(dir, *module, names); + if (r < 0) + return r; + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run);