diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ca43d1c009..2d178990e10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,13 @@ find_package(Ldap) find_package(OpenDbx) find_package(PCRE2 REQUIRED) +foreach(_perl_cache_var IN ITEMS PERL_INCLUDE_PATH PERL_LIBRARY) + if(DEFINED ${_perl_cache_var} AND NOT "${${_perl_cache_var}}" STREQUAL "" AND NOT EXISTS "${${_perl_cache_var}}") + message(STATUS "Resetting stale ${_perl_cache_var} cache entry: ${${_perl_cache_var}}") + unset(${_perl_cache_var} CACHE) + endif() +endforeach() + find_package(PerlLibs) find_package(Popt) find_package(Systemd) @@ -226,7 +233,7 @@ endif() mark_as_advanced(ENV_PRESENT VALGRIND_PRESENT) find_program(ENV_PRESENT env) find_program(VALGRIND_PRESENT valgrind) -find_program(ASCIIDOC_EXECUTABLE asciidoc) +find_program(ASCIIDOC_EXECUTABLE NAMES asciidoc asciidoctor) find_program(SED_EXECUTABLE sed) find_program(GIT_EXECUTABLE git) @@ -529,8 +536,16 @@ if (MSVC) endif() if (${CMAKE_C_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_C_COMPILER_ID} STREQUAL "Clang") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -W -Wall -Wnonnull -Wshadow -Wformat -Wundef -Wno-unused-parameter -Wmissing-prototypes -Wno-unknown-pragmas -Wno-int-conversion -Werror=implicit-function-declaration -D_GNU_SOURCE -DRBT_IMPLICIT_LOCKING=1 -std=c99") - add_link_options(-Wl,-z,now) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -W -Wall -Wnonnull -Wshadow -Wformat -Wundef -Wno-unused-parameter -Wmissing-prototypes -Wno-unknown-pragmas -Wno-int-conversion -Werror=implicit-function-declaration -DRBT_IMPLICIT_LOCKING=1 -std=c99") + # -D_GNU_SOURCE exposes GNU extensions but changes function signatures on non-glibc + # platforms (e.g. strerror_r on macOS becomes XSI). Only set it on glibc systems. + if(NOT APPLE AND NOT CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_GNU_SOURCE") + endif() + # -Wl,-z,now is a Linux/ELF linker flag; not supported on macOS (uses -bind_at_load) + if(NOT APPLE) + add_link_options(-Wl,-z,now) + endif() endif() if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") add_link_options(-lkvm -lm -lprocstat) diff --git a/docs/contribute/testing.adoc b/docs/contribute/testing.adoc index 837faa5ebb3..eb2c81c3f3f 100644 --- a/docs/contribute/testing.adoc +++ b/docs/contribute/testing.adoc @@ -25,6 +25,23 @@ library and then install additional packages required for testing. See the *Building OpenSCAP on Linux* section in the link:../developer/developer.adoc[OpenSCAP Developer Manual] for more details. +For platform-focused validation, prefer CTest labels over ad-hoc test lists. +For example, after a successful non-Linux build you can run: + +---- +$ ctest -L macos +$ ctest -L freebsd +---- + +For a containerized Linux full-suite run, make sure a local SMTP listener and a +session D-Bus are available before invoking CTest, otherwise MITRE, `fwupd`, +and `systemd` coverage may be skipped or fail for environmental reasons: + +---- +$ postfix start +$ dbus-run-session -- ctest --output-on-failure +---- + == Writing a new test In this guide we will use an example to describe the process of writing a test diff --git a/docs/developer/developer.adoc b/docs/developer/developer.adoc index 8bb4f86d63e..13fc72d8ddf 100644 --- a/docs/developer/developer.adoc +++ b/docs/developer/developer.adoc @@ -97,6 +97,12 @@ $ cmake ../ $ make ---- +If you reuse an existing build tree after a Perl upgrade or package-manager +change, CMake may retain stale `PERL_INCLUDE_PATH` or `PERL_LIBRARY` cache +entries. The top-level build now clears cached Perl paths that no longer exist, +so rerunning `cmake ../` in the same build directory is usually sufficient. +If Perl detection still looks wrong, remove `CMakeCache.txt` and reconfigure. + On Ubuntu 18.04 and potentially other distro, the python3 dist-packages path is wrong. If the following command: @@ -168,6 +174,43 @@ Now you can execute the following command to run library self-checks: $ ctest ---- +For containerized Linux validation, start a local MTA and provide a session +D-Bus before invoking the full suite so MITRE, `fwupd`, and `systemd`-related +coverage stays active: + +---- +$ postfix start +$ dbus-run-session -- ctest --output-on-failure +---- + +The test suite supports filtering by labels. This is useful for platform-specific +or subsystem-specific runs: + +---- +$ ctest -L probes +$ ctest -L api +$ ctest -L unix +$ ctest -L independent +$ ctest -L linux_only +$ ctest -L macos +$ ctest -L freebsd +---- + +Labels are assigned in `tests/CMakeLists.txt` by helper functions: + +* `add_oscap_test(script.sh [LABELS ...])`: +** registers shell-based tests, +** always adds `shell`, +** automatically adds a suite label based on the top-level test path + (`api`, `probes`, `report`, `sources`, etc.), +** appends optional explicit labels (for example `unix`, `linux_only`, `macos`). + +* `add_oscap_ctest(name COMMAND ... [LABELS ...])`: +** registers direct CTest commands (for example Python/pytest tests), +** always adds `ctest`, +** automatically adds the same top-level suite label, +** appends optional explicit labels. + Note that using the `--jobs/-j` flag is currently not supported. It will cause unexpected test failures. See link:https://github.com/OpenSCAP/openscap/issues/2057[#2057] for more details. @@ -208,6 +251,55 @@ $ docker build --tag openscap_mitre_tests:latest -f Dockerfiles/mitre_tests . && -- +== Building on macOS and FreeBSD + +OpenSCAP can be built on macOS and FreeBSD with a reduced feature set depending +on available libraries and enabled probes. + +Typical configuration starts with: + +---- +$ mkdir -p build && cd build +$ cmake .. -DENABLE_TESTS=ON -DENABLE_PROBES_LINUX=OFF +$ make +---- + +Notes: + +* Linux-specific probes (`ENABLE_PROBES_LINUX`) should be disabled on non-Linux + systems unless you are explicitly cross-compiling for Linux. +* Some tests are intentionally labeled `linux_only` and should be filtered out + using CTest labels. +* After a successful non-Linux build, `ctest -L macos` or `ctest -L freebsd` + provides a quick portability smoke test without pulling in Linux-only cases. +* On macOS, `SCE` is disabled by default in the main CMake configuration. + +=== Recent portability updates + +The codebase contains recent portability work for macOS/FreeBSD, including: + +* `sysctl` probe support for macOS (`/usr/sbin/sysctl -ae`) and FreeBSD/macOS + branching, including parsing of multiline BSD `sysctl -ae` values by treating + only valid `name=value` headers as new items, +* `memusage` support on macOS via Mach APIs, +* `XCCDF` target MAC collection on macOS via `AF_LINK`, +* fallback parser for password probe offline mode on systems without + `fgetpwent(3)`, +* shadow probe offline mode explicitly marked unsupported on platforms where + the Linux-style shadow path does not apply, +* runlevel probe behavior explicitly marked unsupported on macOS/FreeBSD + (SysV runlevels are Linux/Solaris specific). + +Targeted regression tests for these portability areas are located in: + +* `tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh` +* `tests/API/probes/test_memusage_platform.sh` +* `tests/probes/password/test_probes_password_offline_fallback.sh` +* `tests/probes/runlevel/test_probes_runlevel_unsupported.sh` +* `tests/probes/shadow/test_probes_shadow_offline_unsupported.sh` +* `tests/probes/sysctl/test_sysctl_probe.sh` +* `tests/probes/sysctl/test_sysctl_probe_all.sh` + . *Install* + -- @@ -369,4 +461,3 @@ For more information about OpenSCAP library, you can refer to this online reference manual: http://static.open-scap.org/openscap-1.2/[OpenSCAP reference manual]. This manual is included in a release tarball and can be regenerated from project sources by Doxygen documentation system. - diff --git a/src/OVAL/probes/unix/interface_probe.c b/src/OVAL/probes/unix/interface_probe.c index 1d2f67deb7e..f4b77d2cce2 100644 --- a/src/OVAL/probes/unix/interface_probe.c +++ b/src/OVAL/probes/unix/interface_probe.c @@ -298,8 +298,10 @@ static void get_flags(const struct ifaddrs *ifa, char ***fp) { if (ifa->ifa_flags & IFF_RENAMING) flags_buf[i++] = "RENAMING"; +#ifdef IFF_NOGROUP if (ifa->ifa_flags & IFF_NOGROUP) flags_buf[i++] = "NOGROUP"; +#endif } flags_buf[i] = NULL; diff --git a/src/OVAL/probes/unix/password_probe.c b/src/OVAL/probes/unix/password_probe.c index 0d9bb2923f2..1f0c8374d2b 100644 --- a/src/OVAL/probes/unix/password_probe.c +++ b/src/OVAL/probes/unix/password_probe.c @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #if defined(OS_APPLE) @@ -67,6 +68,79 @@ #include #include "password_probe.h" +/* + * fgetpwent() is a GNU/glibc extension; provide a portable fallback. + * This parses a standard /etc/passwd-format file one entry at a time. + */ +#if defined(OS_APPLE) || defined(OS_FREEBSD) +static int oscap_parse_passwd_id(const char *field, uintmax_t max_value, + uintmax_t *parsed_value) +{ + char *endptr; + uintmax_t value; + + errno = 0; + value = strtoumax(field, &endptr, 10); + if (errno != 0 || endptr == field || *endptr != '\0' || value > max_value) + return -1; + + *parsed_value = value; + return 0; +} + +static struct passwd *oscap_fgetpwent(FILE *fp) +{ + static char line[2048]; + static struct passwd pw; + char *fields[7], *newline, *line_end, *p; + uintmax_t parsed_uid, parsed_gid; + int f; + + while (fgets(line, sizeof(line), fp) != NULL) { + newline = memchr(line, '\n', sizeof(line) - 1); + line_end = memchr(line, '\0', sizeof(line)); + if (newline != NULL) + *newline = '\0'; + else if (line_end == &line[sizeof(line) - 1]) { + int ch; + + /* Skip truncated records instead of parsing partial passwd entries. */ + while ((ch = fgetc(fp)) != '\n' && ch != EOF) + ; + continue; + } + + if (line[0] == '#') + continue; + f = 0; + p = line; + while (f < 7) { + fields[f++] = p; + p = strchr(p, ':'); + if (p) + *p++ = '\0'; + else + break; + } + if (f < 7) + continue; + if (oscap_parse_passwd_id(fields[2], (uintmax_t)(uid_t)-1, &parsed_uid) != 0 || + oscap_parse_passwd_id(fields[3], (uintmax_t)(gid_t)-1, &parsed_gid) != 0) + continue; + pw.pw_name = fields[0]; + pw.pw_passwd = fields[1]; + pw.pw_uid = (uid_t)parsed_uid; + pw.pw_gid = (gid_t)parsed_gid; + pw.pw_gecos = fields[4]; + pw.pw_dir = fields[5]; + pw.pw_shell = fields[6]; + return &pw; + } + return NULL; +} +#define fgetpwent oscap_fgetpwent +#endif + /* Convenience structure for the results being reported */ struct result_info { const char *username; diff --git a/src/OVAL/probes/unix/runlevel_probe.c b/src/OVAL/probes/unix/runlevel_probe.c index ae6fc0c1971..3f28fc51c66 100644 --- a/src/OVAL/probes/unix/runlevel_probe.c +++ b/src/OVAL/probes/unix/runlevel_probe.c @@ -468,9 +468,10 @@ static int get_runlevel (struct runlevel_req *req, struct runlevel_rep **rep) _A(rep != NULL); return GET_RUNLEVEL(LINUX_DISTRO, req, rep); } -#elif defined(OS_FREEBSD) +#elif defined(OS_FREEBSD) || defined(OS_APPLE) static int get_runlevel (struct runlevel_req *req, struct runlevel_rep **rep) { + /* SysV runlevels are a Linux/Solaris concept; not applicable on BSD/macOS */ _A(req != NULL); _A(rep != NULL); return (-1); diff --git a/src/OVAL/probes/unix/shadow_probe.c b/src/OVAL/probes/unix/shadow_probe.c index a57991ccba5..dd5ca66299a 100644 --- a/src/OVAL/probes/unix/shadow_probe.c +++ b/src/OVAL/probes/unix/shadow_probe.c @@ -62,6 +62,11 @@ #include "shadow_probe.h" #ifndef HAVE_SHADOW_H +int shadow_probe_offline_mode_supported() +{ + return PROBE_OFFLINE_NONE; +} + int shadow_probe_main(probe_ctx *ctx, void *arg) { SEXP_t *item_sexp; diff --git a/src/OVAL/probes/unix/sysctl_probe.c b/src/OVAL/probes/unix/sysctl_probe.c index b7c68a03785..7c49164144a 100644 --- a/src/OVAL/probes/unix/sysctl_probe.c +++ b/src/OVAL/probes/unix/sysctl_probe.c @@ -36,14 +36,30 @@ #include "sysctl_probe.h" #if defined(OS_FREEBSD) +#include #include #include #include #include +#include #include "common/debug_priv.h" #define SYSCTL_CMD "/sbin/sysctl -ae" +#define SYSCTL_VALUE_MAX (64 * 1024) +#elif defined(OS_APPLE) +#include +#include +#include +#include +#include +#include + +#include "common/debug_priv.h" + +/* On macOS sysctl(8) lives under /usr/sbin */ +#define SYSCTL_CMD "/usr/sbin/sysctl -ae" +#define SYSCTL_VALUE_MAX (64 * 1024) #endif #if defined(OS_LINUX) @@ -305,7 +321,223 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) return (0); } -#elif defined(OS_FREEBSD) +#elif defined(OS_FREEBSD) || defined(OS_APPLE) + +static int sysctl_name_char(int ch) +{ + return isalnum((unsigned char) ch) || ch == '.' || ch == '_' || + ch == '%' || ch == '-'; +} + +static int sysctl_parse_line(char *line, char **mib, char **sysval) +{ + char *sep; + size_t i, mib_len; + + sep = strchr(line, '='); + if (sep == NULL) + return 0; + + mib_len = (size_t)(sep - line); + if (mib_len == 0 || !isalpha((unsigned char) line[0])) + return 0; + + for (i = 0; i < mib_len; ++i) { + if (!sysctl_name_char(line[i])) + return 0; + } + + *sep = '\0'; + *mib = line; + *sysval = sep + 1; + return 1; +} + +static int sysctl_append_value(char **value, size_t *value_len, const char *line, int *truncated) +{ + size_t line_len, append_len, new_len; + char *new_value; + + if (*truncated) + return 0; + + line_len = strlen(line); + if (*value_len >= SYSCTL_VALUE_MAX) { + *truncated = 1; + return 0; + } + + append_len = line_len; + if (*value_len + append_len > SYSCTL_VALUE_MAX) + append_len = SYSCTL_VALUE_MAX - *value_len; + + new_len = *value_len + append_len + (*value_len > 0 ? 1 : 0) + 1; + new_value = realloc(*value, new_len); + if (new_value == NULL) + return -1; + + *value = new_value; + if (*value_len > 0) + new_value[(*value_len)++] = '\n'; + + memcpy(new_value + *value_len, line, append_len); + *value_len += append_len; + new_value[*value_len] = '\0'; + + if (append_len < line_len) + *truncated = 1; + + return 0; +} + +static char *sysctl_sanitize_single_value(const char *value) +{ + size_t i, in_len, out_len = 0; + char *sanitized; + + in_len = strlen(value); + sanitized = malloc(in_len + 1); + if (sanitized == NULL) + return NULL; + + for (i = 0; i < in_len; ++i) { + unsigned char ch = (unsigned char) value[i]; + + if (!isprint(ch) && !isspace(ch)) + continue; + + sanitized[out_len++] = value[i]; + } + + while (out_len > 0 && sanitized[out_len - 1] == '\n') + --out_len; + + sanitized[out_len] = '\0'; + return sanitized; +} + +static int sysctl_sanitize_multi_value(const char *value, char **buffer_out, char ***values_out) +{ + size_t i, in_len, value_count = 0, value_index = 0; + int in_value = 0; + char *buffer; + char **values; + + in_len = strlen(value); + buffer = malloc(in_len + 1); + if (buffer == NULL) + return -1; + + memcpy(buffer, value, in_len + 1); + + for (i = 0; i < in_len; ++i) { + unsigned char ch = (unsigned char) buffer[i]; + + if ((!isprint(ch) && !isspace(ch)) || buffer[i] == '\n') + buffer[i] = '\0'; + + if (buffer[i] == '\0') { + in_value = 0; + continue; + } + + if (!in_value) { + ++value_count; + in_value = 1; + } + } + + if (value_count == 0) + value_count = 1; + + values = calloc(value_count + 1, sizeof(char *)); + if (values == NULL) { + free(buffer); + return -1; + } + + if (buffer[0] == '\0' && value_count == 1) { + values[0] = buffer; + *buffer_out = buffer; + *values_out = values; + return 0; + } + + in_value = 0; + for (i = 0; i < in_len; ++i) { + if (buffer[i] == '\0') { + in_value = 0; + continue; + } + + if (!in_value) { + values[value_index++] = buffer + i; + in_value = 1; + } + } + + *buffer_out = buffer; + *values_out = values; + return 0; +} + +static int sysctl_collect_item(probe_ctx *ctx, SEXP_t *name_entity, + const char *mib, const char *sysval, int over_cmp) +{ + SEXP_t *item, *se_mib; + char *sanitized_value = NULL; + char *sanitized_buffer = NULL; + char **sanitized_values = NULL; + int ret = 0; + + se_mib = SEXP_string_new(mib, strlen(mib)); + if (!se_mib) { + dE("Failed to allocate new SEXP_string for se_mib"); + return PROBE_ENOENT; + } + + if (probe_entobj_cmp(name_entity, se_mib) == OVAL_RESULT_TRUE) { + if (over_cmp >= 0) { + if (sysctl_sanitize_multi_value(sysval, &sanitized_buffer, &sanitized_values) != 0) { + dE("Failed to sanitize sysctl value list"); + ret = PROBE_ENOENT; + goto cleanup; + } + + item = probe_item_create(OVAL_UNIX_SYSCTL, NULL, + "name", OVAL_DATATYPE_SEXP, se_mib, + "value", OVAL_DATATYPE_STRING_M, sanitized_values, + NULL); + } else { + sanitized_value = sysctl_sanitize_single_value(sysval); + if (sanitized_value == NULL) { + dE("Failed to sanitize sysctl value"); + ret = PROBE_ENOENT; + goto cleanup; + } + + item = probe_item_create(OVAL_UNIX_SYSCTL, NULL, + "name", OVAL_DATATYPE_SEXP, se_mib, + "value", OVAL_DATATYPE_STRING, sanitized_value, + NULL); + } + + if (!item) { + dE("probe_item_create() returned a null item"); + ret = PROBE_ENOENT; + goto cleanup; + } + + probe_item_collect(ctx, item); + } + +cleanup: + free(sanitized_value); + free(sanitized_buffer); + free(sanitized_values); + SEXP_free(se_mib); + return ret; +} int sysctl_probe_offline_mode_supported(void) { @@ -315,15 +547,22 @@ int sysctl_probe_offline_mode_supported(void) int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) { FILE *fp; - char output[LINE_MAX]; - const char* SEP = "="; - char* mib; - char* sysval; - SEXP_t *se_mib; + char *output = NULL; + size_t output_cap = 0; + ssize_t output_len; SEXP_t *name_entity, *probe_in; + oval_schema_version_t over; + int over_cmp; + int ret = 0; + char *current_mib = NULL; + char *current_value = NULL; + size_t current_value_len = 0; + int current_value_truncated = 0; probe_in = probe_ctx_getobject(ctx); name_entity = probe_obj_getent(probe_in, "name", 1); + over = probe_obj_get_platform_schema_version(probe_in); + over_cmp = oval_schema_version_cmp(over, OVAL_SCHEMA_VERSION(5.10)); if (name_entity == NULL) { dE("Missing \"name\" entity in the input object"); @@ -343,51 +582,72 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) return (PROBE_EFATAL); } - while (fgets(output, sizeof(output), fp)) { - char *strp; - mib = strtok_r(output, SEP, &strp); - sysval = strtok_r(NULL, SEP, &strp); - - if (!mib) - continue; - - if (!sysval) - continue; - - se_mib = SEXP_string_new(mib, strlen(mib)); - - if (!se_mib) { - dE("Failed to allocate new SEXP_string for se_mib"); - pclose(fp); - return (PROBE_ENOENT); - } - - /* Remove newline */ - sysval[strlen(sysval)-1] = '\0'; - - if (probe_entobj_cmp(name_entity, se_mib) == OVAL_RESULT_TRUE) { - SEXP_t *item; - - item = probe_item_create(OVAL_UNIX_SYSCTL, NULL, - "name", OVAL_DATATYPE_SEXP, se_mib, - "value", OVAL_DATATYPE_STRING, sysval, - NULL); - - if (!item) { - dE("probe_item_create() returned a null item"); - pclose(fp); - SEXP_free(se_mib); - return (PROBE_ENOENT); - } - - probe_item_collect(ctx, item); - } - - SEXP_free(se_mib); + while ((output_len = getline(&output, &output_cap, fp)) != -1) { + char *mib, *sysval, *newline; + + newline = strchr(output, '\n'); + if (newline != NULL) + *newline = '\0'; + + if (sysctl_parse_line(output, &mib, &sysval)) { + if (current_mib != NULL) { + ret = sysctl_collect_item(ctx, name_entity, current_mib, + current_value != NULL ? current_value : "", + over_cmp); + if (ret != 0) + goto cleanup; + } + + free(current_mib); + free(current_value); + current_mib = strdup(mib); + current_value = NULL; + current_value_len = 0; + current_value_truncated = 0; + if (current_mib == NULL) { + dE("Failed to allocate new sysctl name buffer"); + ret = PROBE_ENOENT; + goto cleanup; + } + + if (sysctl_append_value(¤t_value, ¤t_value_len, sysval, + ¤t_value_truncated) != 0) { + dE("Failed to append sysctl value"); + ret = PROBE_ENOENT; + goto cleanup; + } + + if (current_value_truncated) + dD("Truncated sysctl value for %s to %d bytes", current_mib, SYSCTL_VALUE_MAX); + + continue; + } + + if (current_mib == NULL) + continue; + + if (sysctl_append_value(¤t_value, ¤t_value_len, output, + ¤t_value_truncated) != 0) { + dE("Failed to append sysctl continuation value"); + ret = PROBE_ENOENT; + goto cleanup; + } + + if (current_value_truncated) + dD("Truncated sysctl value for %s to %d bytes", current_mib, SYSCTL_VALUE_MAX); } + if (current_mib != NULL) + ret = sysctl_collect_item(ctx, name_entity, current_mib, + current_value != NULL ? current_value : "", + over_cmp); + +cleanup: + free(output); + free(current_mib); + free(current_value); pclose(fp); - return (0); + return ret; } #else diff --git a/src/XCCDF/result.c b/src/XCCDF/result.c index 9117bd22a1e..fab6bc512c3 100644 --- a/src/XCCDF/result.c +++ b/src/XCCDF/result.c @@ -70,6 +70,16 @@ #include #endif +#if defined(OS_APPLE) +#include +#include +#include +#include +#include +#include +#include +#endif + #include "item.h" #include "helpers.h" #include "xccdf_impl.h" @@ -468,6 +478,58 @@ void xccdf_result_fill_sysinfo(struct xccdf_result *result) out1: freeifaddrs(ifaddr); } +#elif defined(OS_APPLE) + if (!probe_root) { + struct ifaddrs *ifaddr, *ifa; + if (getifaddrs(&ifaddr) == -1) + return; + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + int family; + char hostip[NI_MAXHOST]; + + if (!ifa->ifa_addr) + continue; + family = ifa->ifa_addr->sa_family; + + if (family == AF_INET || family == AF_INET6) { + if (family == AF_INET) { + if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), + hostip, sizeof(hostip), NULL, 0, NI_NUMERICHOST)) + continue; + } else { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + if (!inet_ntop(family, &sin6->sin6_addr, hostip, sizeof(hostip))) + continue; + } + xccdf_result_add_target_address(result, hostip); + fact = xccdf_target_fact_new(); + xccdf_target_fact_set_name(fact, family == AF_INET ? + "urn:xccdf:fact:asset:identifier:ipv4" : + "urn:xccdf:fact:asset:identifier:ipv6"); + xccdf_target_fact_set_string(fact, hostip); + _xccdf_result_add_target_fact_uniq(result, fact); + } else if (family == AF_LINK) { + /* macOS exposes MAC addresses as AF_LINK entries in getifaddrs */ + struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr; + if (sdl->sdl_alen == 6) { + unsigned char *mac = (unsigned char *)LLADDR(sdl); + char macbuf[20]; + snprintf(macbuf, sizeof(macbuf), + "%02X:%02X:%02X:%02X:%02X:%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + fact = xccdf_target_fact_new(); + xccdf_target_fact_set_name(fact, "urn:xccdf:fact:ethernet:MAC"); + xccdf_target_fact_set_string(fact, macbuf); + _xccdf_result_add_target_fact_uniq(result, fact); + fact = xccdf_target_fact_new(); + xccdf_target_fact_set_name(fact, "urn:xccdf:fact:asset:identifier:mac"); + xccdf_target_fact_set_string(fact, macbuf); + _xccdf_result_add_target_fact_uniq(result, fact); + } + } + } + freeifaddrs(ifaddr); + } #elif defined(OS_WINDOWS) #define VERSION_LEN 32 diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 5e4a0b11168..069df288c98 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -412,6 +412,10 @@ static inline int _xccdf_fix_decode_xml(struct xccdf_fix *fix, char **result) #if defined(unix) || defined(__unix__) || defined(__unix) static inline int _xccdf_fix_execute(struct xccdf_rule_result *rr, struct xccdf_fix *fix) { +#ifndef OS_WINDOWS + extern char **environ; +#endif + if (rr == NULL) { return 1; } @@ -2068,4 +2072,3 @@ int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result * return ret; } - diff --git a/src/common/memusage.c b/src/common/memusage.c index f6baca32983..8d75ee09c1c 100644 --- a/src/common/memusage.c +++ b/src/common/memusage.c @@ -27,16 +27,14 @@ #include #include +#include #include #include #include #include #if defined(OS_FREEBSD) -#include -#include #include -#include #include #include #include @@ -50,41 +48,46 @@ #define GET_VM_ACT_PAGE_COUNT "vm.stats.vm.v_active_count" #define BYTES_TO_KIB(x) (x >> 10) -#endif +#endif /* OS_FREEBSD */ + +#if defined(OS_APPLE) +#include +#include +#define BYTES_TO_KIB(x) ((x) >> 10) +#endif /* OS_APPLE */ #include "debug_priv.h" #include "memusage.h" #include "bfind.h" -#if defined(OS_LINUX) || defined(__FreeBSD__) || defined(OS_SOLARIS) -static int read_common_sizet(void *szp, char *strval) +#if defined(OS_LINUX) || defined(__FreeBSD__) || defined(OS_SOLARIS) || defined(OSCAP_TEST_READ_COMMON_SIZET) +static int read_common_sizet(void *szp, const char *strval) { char *end; + long long value; if (szp == NULL || strval == NULL) { return -1; } - end = strchr(strval, ' '); - - if (end == NULL) - return (-1); - - *end = '\0'; - errno = 0; - *(size_t *)szp = strtoll(strval, NULL, 10); + value = strtoll(strval, &end, 10); - if (errno == EINVAL || - errno == ERANGE) - return (-1); + if (end == strval || !isspace((unsigned char)*end) || + errno == EINVAL || errno == ERANGE || value < 0) + return -1; - return (0); + *(size_t *)szp = (size_t)value; + return 0; } +#endif + +#if defined(OS_LINUX) || defined(__FreeBSD__) || defined(OS_SOLARIS) + struct stat_parser { char *keyword; - int (*storval)(void *, char *); + int (*storval)(void *, const char *); ptrdiff_t offset; }; @@ -252,33 +255,39 @@ static int freebsd_sys_memusage(struct sys_memusage *mu) static int freebsd_proc_memusage(struct proc_memusage *mu) { - int count; - kvm_t *kd; pid_t mypid; - char errbuf[LINE_MAX]; - struct kinfo_proc *procinfo; + struct kinfo_proc procinfo; + size_t size; + size_t page_size; + int mib[4]; mypid = getpid(); - kd = kvm_openfiles(NULL, _PATH_DEVNULL, NULL, O_RDONLY, errbuf); - - if (!kd) + size = sizeof(procinfo); + memset(&procinfo, 0, sizeof(procinfo)); + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = mypid; + + if (sysctl(mib, 4, &procinfo, &size, NULL, 0) < 0) return -1; - procinfo = kvm_getprocs(kd, KERN_PROC_PID, mypid, &count); - - if (!procinfo) + if (size == 0) { + errno = ESRCH; return -1; + } - mu->mu_rss = procinfo->ki_rssize; - mu->mu_text = procinfo->ki_tsize; - mu->mu_data = procinfo->ki_dsize; - mu->mu_stack = procinfo->ki_ssize; + page_size = (size_t)getpagesize(); + mu->mu_rss = BYTES_TO_KIB((uint64_t)procinfo.ki_rssize * page_size); + mu->mu_text = BYTES_TO_KIB((uint64_t)procinfo.ki_tsize * page_size); + mu->mu_data = BYTES_TO_KIB((uint64_t)procinfo.ki_dsize * page_size); + mu->mu_stack = BYTES_TO_KIB((uint64_t)procinfo.ki_ssize * page_size); /* ki_swrss is the resident set size before last swap, this * is the closest approximation to Linux's "VmHWM" which is the * peak resident set size of the process. */ - mu->mu_hwm = procinfo->ki_swrss; + mu->mu_hwm = BYTES_TO_KIB((uint64_t)procinfo.ki_swrss * page_size); /* Not exposed on FreeBSD */ mu->mu_lib = 0; @@ -305,6 +314,32 @@ int oscap_sys_memusage(struct sys_memusage *mu) #elif defined(OS_FREEBSD) if (freebsd_sys_memusage(mu)) return -1; +#elif defined(OS_APPLE) + { + vm_statistics64_data_t vm_stat; + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + vm_size_t page_size; + host_page_size(mach_host_self(), &page_size); + if (host_statistics64(mach_host_self(), HOST_VM_INFO64, + (host_info64_t)&vm_stat, &count) != KERN_SUCCESS) { + errno = EOPNOTSUPP; + return -1; + } + mu->mu_free = BYTES_TO_KIB((uint64_t)vm_stat.free_count * page_size); + mu->mu_active = BYTES_TO_KIB((uint64_t)vm_stat.active_count * page_size); + mu->mu_inactive = BYTES_TO_KIB((uint64_t)vm_stat.inactive_count * page_size); + mu->mu_buffers = 0; + mu->mu_cached = 0; + mu->mu_realfree = mu->mu_free + mu->mu_inactive; + /* Query total physical RAM via sysctl HW_MEMSIZE */ + int mib[2] = { CTL_HW, HW_MEMSIZE }; + uint64_t memsize = 0; + size_t len = sizeof(memsize); + if (sysctl(mib, 2, &memsize, &len, NULL, 0) == 0) + mu->mu_total = BYTES_TO_KIB(memsize); + else + mu->mu_total = 0; + } #else errno = EOPNOTSUPP; return -1; @@ -326,6 +361,24 @@ int oscap_proc_memusage(struct proc_memusage *mu) #elif defined(OS_FREEBSD) if (freebsd_proc_memusage(mu)) return -1; +#elif defined(OS_APPLE) + { + struct task_basic_info info; + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + if (task_info(mach_task_self(), TASK_BASIC_INFO, + (task_info_t)&info, &count) != KERN_SUCCESS) { + errno = EOPNOTSUPP; + return -1; + } + mu->mu_rss = BYTES_TO_KIB(info.resident_size); + mu->mu_data = BYTES_TO_KIB(info.virtual_size); + /* TASK_BASIC_INFO doesn't expose peak RSS; use current as approximation */ + mu->mu_hwm = mu->mu_rss; + mu->mu_text = 0; + mu->mu_stack = 0; + mu->mu_lib = 0; + mu->mu_lock = 0; + } #else errno = EOPNOTSUPP; return -1; diff --git a/tests/API/XCCDF/unittests/CMakeLists.txt b/tests/API/XCCDF/unittests/CMakeLists.txt index 674e2b29b1f..051614c8b8e 100644 --- a/tests/API/XCCDF/unittests/CMakeLists.txt +++ b/tests/API/XCCDF/unittests/CMakeLists.txt @@ -17,6 +17,10 @@ add_oscap_test_executable(test_xccdf_shall_pass ) target_link_libraries(test_xccdf_shall_pass openscap ${LIBXML2_LIBRARIES} ${LIBXSLT_LIBRARIES} ${LIBXSLT_EXSLT_LIBRARIES} ${PCRE2_LIBRARIES} ${CURL_LIBRARIES} ${RPM_LIBRARIES}) +add_oscap_test_executable(test_xccdf_result_sysinfo_platform + "test_xccdf_result_sysinfo_platform.c" +) + if(PYTHONINTERP_FOUND) add_oscap_test("all_python.sh") endif() @@ -109,6 +113,7 @@ add_oscap_test("test_fix_resultid_by_suffix.sh") add_oscap_test("test_generate_fix_ansible_vars.sh") add_oscap_test("test_xccdf_requires_conflicts.sh") add_oscap_test("test_results_hostname.sh") +add_oscap_test("test_xccdf_result_sysinfo_platform.sh" LABELS macos freebsd) add_oscap_test("test_skip_rule.sh") add_oscap_test("test_no_newline_between_select_elements.sh") add_oscap_test("test_single_line_tailoring.sh") diff --git a/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.c b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.c new file mode 100644 index 00000000000..311de638cbb --- /dev/null +++ b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "xccdf_benchmark.h" + +#if defined(OS_APPLE) || defined(OS_FREEBSD) +#define EXPECT_MAC_FACTS 1 +#endif + +int main(void) +{ + struct xccdf_result *result = xccdf_result_new(); + struct xccdf_target_fact_iterator *facts = NULL; +#if defined(EXPECT_MAC_FACTS) + bool saw_mac = false; + bool saw_asset_mac = false; +#endif + + xccdf_result_fill_sysinfo(result); + facts = xccdf_result_get_target_facts(result); + + while (xccdf_target_fact_iterator_has_more(facts)) { + const struct xccdf_target_fact *fact = xccdf_target_fact_iterator_next(facts); + const char *name = xccdf_target_fact_get_name(fact); + + if (name == NULL) + continue; + +#if defined(EXPECT_MAC_FACTS) + if (strcmp(name, "urn:xccdf:fact:ethernet:MAC") == 0) + saw_mac = true; + if (strcmp(name, "urn:xccdf:fact:asset:identifier:mac") == 0) + saw_asset_mac = true; +#endif + } + + xccdf_target_fact_iterator_free(facts); + xccdf_result_free(result); + +#if defined(EXPECT_MAC_FACTS) + if (!saw_mac) { + fprintf(stderr, "Expected at least one urn:xccdf:fact:ethernet:MAC fact on this platform.\n"); + return 1; + } + if (!saw_asset_mac) { + fprintf(stderr, "Expected at least one urn:xccdf:fact:asset:identifier:mac fact on this platform.\n"); + return 1; + } +#endif + + return 0; +} diff --git a/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh new file mode 100755 index 00000000000..89c3cc46801 --- /dev/null +++ b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +. $builddir/tests/test_common.sh + +if [[ -n "${CUSTOM_OSCAP+x}" ]] ; then + exit 255 +fi + +case "$(uname)" in + Darwin|FreeBSD) ;; + *) exit 255 ;; +esac + +./test_xccdf_result_sysinfo_platform diff --git a/tests/API/probes/CMakeLists.txt b/tests/API/probes/CMakeLists.txt index cd5ca8358d3..5f31ebbb84f 100644 --- a/tests/API/probes/CMakeLists.txt +++ b/tests/API/probes/CMakeLists.txt @@ -8,7 +8,7 @@ target_include_directories(test_api_probes_smoke PUBLIC "${CMAKE_SOURCE_DIR}/src/OVAL/probes/public" "${CMAKE_SOURCE_DIR}/src/common" ) -add_oscap_test("test_api_probes_smoke.sh") +add_oscap_test("test_api_probes_smoke.sh" LABELS api probes) add_oscap_test_executable(test_fsdev_is_local_fs "test_fsdev_is_local_fs.c" @@ -16,7 +16,7 @@ add_oscap_test_executable(test_fsdev_is_local_fs target_include_directories(test_fsdev_is_local_fs PUBLIC "${CMAKE_SOURCE_DIR}/src/OVAL/probes" ) -add_oscap_test("test_fsdev_is_local_fs.sh") +add_oscap_test("test_fsdev_is_local_fs.sh" LABELS api probes) file(GLOB_RECURSE OVAL_RESULTS_SOURCES "${CMAKE_SOURCE_DIR}/src/OVAL/results/oval_cmp*.c") add_oscap_test_executable(oval_fts_list @@ -38,7 +38,7 @@ target_include_directories(oval_fts_list PUBLIC "${CMAKE_SOURCE_DIR}/src/common" ) target_link_libraries(oval_fts_list openscap) -add_oscap_test("fts.sh") +add_oscap_test("fts.sh" LABELS api probes) add_oscap_test_executable(test_memusage "test_memusage.c" @@ -47,4 +47,25 @@ add_oscap_test_executable(test_memusage target_include_directories(test_memusage PUBLIC "${CMAKE_SOURCE_DIR}/src/common" ) -add_oscap_test("test_memusage.sh") +add_oscap_test("test_memusage.sh" LABELS api probes) + +add_oscap_test_executable(test_memusage_platform + "test_memusage_platform.c" + "${CMAKE_SOURCE_DIR}/src/common/memusage.c" + "${CMAKE_SOURCE_DIR}/src/common/bfind.c" +) +target_include_directories(test_memusage_platform PUBLIC + "${CMAKE_SOURCE_DIR}/src/common" +) +add_oscap_test("test_memusage_platform.sh" LABELS api probes freebsd macos linux) + +add_oscap_test_executable(test_memusage_freebsd_stress + "test_memusage_freebsd_stress.c" + "${CMAKE_SOURCE_DIR}/src/common/memusage.c" + "${CMAKE_SOURCE_DIR}/src/common/bfind.c" +) +target_include_directories(test_memusage_freebsd_stress PUBLIC + "${CMAKE_SOURCE_DIR}/src/common" +) +target_link_libraries(test_memusage_freebsd_stress Threads::Threads) +add_oscap_test("test_memusage_freebsd_stress.sh" LABELS api probes freebsd) diff --git a/tests/API/probes/test_memusage.c b/tests/API/probes/test_memusage.c index b9db865d45d..08230520bb6 100644 --- a/tests/API/probes/test_memusage.c +++ b/tests/API/probes/test_memusage.c @@ -6,8 +6,8 @@ #include #include "memusage.h" +#define OSCAP_TEST_READ_COMMON_SIZET #include "memusage.c" -#define OS_LINUX static int test_basic() { diff --git a/tests/API/probes/test_memusage_freebsd_stress.c b/tests/API/probes/test_memusage_freebsd_stress.c new file mode 100644 index 00000000000..b7a9e0e8a0d --- /dev/null +++ b/tests/API/probes/test_memusage_freebsd_stress.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#if defined(OS_FREEBSD) +#include +#endif +#include + +#include "memusage.h" + +#if defined(OS_FREEBSD) +#define STRESS_THREADS 4 +#define STRESS_ITERATIONS 2000 + +struct thread_result { + int err; +}; + +static void *worker(void *arg) +{ + struct thread_result *result = arg; + int i; + + for (i = 0; i < STRESS_ITERATIONS; ++i) { + struct proc_memusage proc_mu = {0}; + + if (oscap_proc_memusage(&proc_mu) != 0) { + result->err = errno ? errno : -1; + return NULL; + } + } + + result->err = 0; + return NULL; +} +#endif + +int main(void) +{ +#if defined(OS_FREEBSD) + pthread_t threads[STRESS_THREADS]; + struct thread_result results[STRESS_THREADS]; + int i; + + for (i = 0; i < STRESS_THREADS; ++i) { + results[i].err = 0; + if (pthread_create(&threads[i], NULL, worker, &results[i]) != 0) { + perror("pthread_create"); + return 1; + } + } + + for (i = 0; i < STRESS_THREADS; ++i) { + if (pthread_join(threads[i], NULL) != 0) { + perror("pthread_join"); + return 1; + } + if (results[i].err != 0) { + fprintf(stderr, "oscap_proc_memusage failed in worker %d with errno=%d\n", i, results[i].err); + return 1; + } + } +#endif + + return 0; +} diff --git a/tests/API/probes/test_memusage_freebsd_stress.sh b/tests/API/probes/test_memusage_freebsd_stress.sh new file mode 100755 index 00000000000..d55c3fdb56c --- /dev/null +++ b/tests/API/probes/test_memusage_freebsd_stress.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +. $builddir/tests/test_common.sh + +if [[ "$(uname)" != "FreeBSD" ]] ; then + exit 255 +fi + +./test_memusage_freebsd_stress diff --git a/tests/API/probes/test_memusage_platform.c b/tests/API/probes/test_memusage_platform.c new file mode 100644 index 00000000000..67a27405d99 --- /dev/null +++ b/tests/API/probes/test_memusage_platform.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "memusage.h" + +int main(void) +{ + struct sys_memusage sys_mu = {0}; + struct proc_memusage proc_mu = {0}; + int ret_sys = oscap_sys_memusage(&sys_mu); + int ret_proc = oscap_proc_memusage(&proc_mu); + +#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_APPLE) + if (ret_sys != 0) { + fprintf(stderr, "oscap_sys_memusage failed with errno=%d\n", errno); + return 1; + } + if (ret_proc != 0) { + fprintf(stderr, "oscap_proc_memusage failed with errno=%d\n", errno); + return 1; + } +#else + if (ret_sys == 0 || ret_proc == 0) { + fprintf(stderr, "memusage unexpectedly supported on this platform\n"); + return 1; + } +#endif + + return 0; +} diff --git a/tests/API/probes/test_memusage_platform.sh b/tests/API/probes/test_memusage_platform.sh new file mode 100755 index 00000000000..845d423035c --- /dev/null +++ b/tests/API/probes/test_memusage_platform.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +. $builddir/tests/test_common.sh + +if [[ -n "${CUSTOM_OSCAP+x}" ]] ; then + exit 255 +fi + +./test_memusage_platform diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2eb0a550cef..e47c9e822df 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,15 +1,66 @@ +include(CMakeParseArguments) + function(add_oscap_test TEST_SCRIPT) + set(options) + set(oneValueArgs) + set(multiValueArgs LABELS) + cmake_parse_arguments(OSCAP_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + string(REPLACE "${CMAKE_SOURCE_DIR}/tests/" "" TEST_NAME "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_SCRIPT}") add_test(NAME ${TEST_NAME} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/${TEST_SCRIPT} ) + set(TEST_LABELS "shell") + # Add a suite label derived from the first path component (api, probes, report, ...) + string(REGEX MATCH "^[^/]+" TEST_SUITE "${TEST_NAME}") + if(TEST_SUITE) + string(TOLOWER "${TEST_SUITE}" TEST_SUITE_LOWER) + list(APPEND TEST_LABELS "${TEST_SUITE_LOWER}") + endif() + if(OSCAP_TEST_LABELS) + list(APPEND TEST_LABELS ${OSCAP_TEST_LABELS}) + endif() + list(REMOVE_DUPLICATES TEST_LABELS) set_tests_properties(${TEST_NAME} PROPERTIES SKIP_RETURN_CODE 255 + LABELS "${TEST_LABELS}" ENVIRONMENT "srcdir=${CMAKE_CURRENT_SOURCE_DIR};top_srcdir=${CMAKE_SOURCE_DIR};builddir=${CMAKE_BINARY_DIR}" ) endfunction() +function(add_oscap_ctest TEST_NAME) + set(options) + set(oneValueArgs) + set(multiValueArgs COMMAND LABELS) + cmake_parse_arguments(OSCAP_CTEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT OSCAP_CTEST_COMMAND) + message(FATAL_ERROR "add_oscap_ctest requires COMMAND arguments") + endif() + + add_test(NAME ${TEST_NAME} + COMMAND ${OSCAP_CTEST_COMMAND} + ) + + set(TEST_LABELS "ctest") + string(REPLACE "${CMAKE_SOURCE_DIR}/tests/" "" TEST_REL_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + string(REGEX MATCH "^[^/]+" TEST_SUITE "${TEST_REL_DIR}") + if(TEST_SUITE) + string(TOLOWER "${TEST_SUITE}" TEST_SUITE_LOWER) + list(APPEND TEST_LABELS "${TEST_SUITE_LOWER}") + endif() + if(OSCAP_CTEST_LABELS) + list(APPEND TEST_LABELS ${OSCAP_CTEST_LABELS}) + endif() + list(REMOVE_DUPLICATES TEST_LABELS) + + set_tests_properties(${TEST_NAME} + PROPERTIES + LABELS "${TEST_LABELS}" + ) +endfunction() + # builds a binary from a C source that will be used in a test # EXECUTABLE_NAME - name of the binary executable to be build # SOURCE_FILE - C program with a test diff --git a/tests/probes/environmentvariable/CMakeLists.txt b/tests/probes/environmentvariable/CMakeLists.txt index 456665964b4..3074eaeef1e 100644 --- a/tests/probes/environmentvariable/CMakeLists.txt +++ b/tests/probes/environmentvariable/CMakeLists.txt @@ -1,3 +1,3 @@ if(ENABLE_PROBES_INDEPENDENT) - add_oscap_test("test_probes_environmentvariable.sh") + add_oscap_test("test_probes_environmentvariable.sh" LABELS independent) endif() diff --git a/tests/probes/environmentvariable58/CMakeLists.txt b/tests/probes/environmentvariable58/CMakeLists.txt index 095f90b01f1..31f6bd732a0 100644 --- a/tests/probes/environmentvariable58/CMakeLists.txt +++ b/tests/probes/environmentvariable58/CMakeLists.txt @@ -1,4 +1,4 @@ if(ENABLE_PROBES_INDEPENDENT) - add_oscap_test("test_probes_environmentvariable58.sh") - add_oscap_test("test_probes_environmentvariable58_offline_mode.sh") + add_oscap_test("test_probes_environmentvariable58.sh" LABELS independent) + add_oscap_test("test_probes_environmentvariable58_offline_mode.sh" LABELS independent) endif() diff --git a/tests/probes/family/CMakeLists.txt b/tests/probes/family/CMakeLists.txt index 136b3f868c4..12b9a41d989 100644 --- a/tests/probes/family/CMakeLists.txt +++ b/tests/probes/family/CMakeLists.txt @@ -1,3 +1,3 @@ if(ENABLE_PROBES_INDEPENDENT) - add_oscap_test("test_probes_family.sh") + add_oscap_test("test_probes_family.sh" LABELS independent) endif() diff --git a/tests/probes/file/CMakeLists.txt b/tests/probes/file/CMakeLists.txt index ba684726358..746be5cf093 100644 --- a/tests/probes/file/CMakeLists.txt +++ b/tests/probes/file/CMakeLists.txt @@ -1,5 +1,5 @@ if(ENABLE_PROBES_UNIX) - add_oscap_test("test_probes_file.sh") - add_oscap_test("test_probes_file_behaviour.sh") - add_oscap_test("test_probes_file_multiple_file_paths.sh") + add_oscap_test("test_probes_file.sh" LABELS unix) + add_oscap_test("test_probes_file_behaviour.sh" LABELS unix) + add_oscap_test("test_probes_file_multiple_file_paths.sh" LABELS unix) endif() diff --git a/tests/probes/fileextendedattribute/CMakeLists.txt b/tests/probes/fileextendedattribute/CMakeLists.txt index bacf604aa59..956e9ae6761 100644 --- a/tests/probes/fileextendedattribute/CMakeLists.txt +++ b/tests/probes/fileextendedattribute/CMakeLists.txt @@ -1,3 +1,3 @@ if(ENABLE_PROBES_UNIX) - add_oscap_test("test_probes_fileextendedattribute.sh") + add_oscap_test("test_probes_fileextendedattribute.sh" LABELS unix) endif() diff --git a/tests/probes/filehash/CMakeLists.txt b/tests/probes/filehash/CMakeLists.txt index cd04df4f308..dab1d4a50ed 100644 --- a/tests/probes/filehash/CMakeLists.txt +++ b/tests/probes/filehash/CMakeLists.txt @@ -1,3 +1,3 @@ if(OPENSCAP_PROBE_INDEPENDENT_FILEHASH) - add_oscap_test("test_probes_filehash.sh") + add_oscap_test("test_probes_filehash.sh" LABELS independent) endif() diff --git a/tests/probes/filehash58/CMakeLists.txt b/tests/probes/filehash58/CMakeLists.txt index cdec0792eb8..bd0bc0a6903 100644 --- a/tests/probes/filehash58/CMakeLists.txt +++ b/tests/probes/filehash58/CMakeLists.txt @@ -1,4 +1,4 @@ if(ENABLE_PROBES_INDEPENDENT) - add_oscap_test("test_probes_filehash58.sh") - add_oscap_test("rhbz1959570_segfault.sh") + add_oscap_test("test_probes_filehash58.sh" LABELS independent) + add_oscap_test("rhbz1959570_segfault.sh" LABELS independent) endif() diff --git a/tests/probes/filemd5/CMakeLists.txt b/tests/probes/filemd5/CMakeLists.txt index 6d4d72cd359..77db217e839 100644 --- a/tests/probes/filemd5/CMakeLists.txt +++ b/tests/probes/filemd5/CMakeLists.txt @@ -1,3 +1,3 @@ if(ENABLE_PROBES_INDEPENDENT) - add_oscap_test("test_probes_filemd5.sh") + add_oscap_test("test_probes_filemd5.sh" LABELS independent) endif() diff --git a/tests/probes/fwupdsecattr/CMakeLists.txt b/tests/probes/fwupdsecattr/CMakeLists.txt index 6c3adde8722..245e368a828 100644 --- a/tests/probes/fwupdsecattr/CMakeLists.txt +++ b/tests/probes/fwupdsecattr/CMakeLists.txt @@ -1,7 +1,7 @@ if(ENABLE_PROBES_LINUX) if(DBUS_FOUND) - add_oscap_test("test_probes_fwupdsecattr.sh") - add_oscap_test("test_probes_fwupdsecattr_mock.sh") + add_oscap_test("test_probes_fwupdsecattr.sh" LABELS linux linux_only) + add_oscap_test("test_probes_fwupdsecattr_mock.sh" LABELS linux linux_only) endif() endif() diff --git a/tests/probes/iflisteners/CMakeLists.txt b/tests/probes/iflisteners/CMakeLists.txt index 83b33f643fb..79653705380 100644 --- a/tests/probes/iflisteners/CMakeLists.txt +++ b/tests/probes/iflisteners/CMakeLists.txt @@ -1,3 +1,3 @@ if(ENABLE_PROBES_LINUX) - add_oscap_test("test_probes_iflisteners.sh") + add_oscap_test("test_probes_iflisteners.sh" LABELS linux linux_only) endif() diff --git a/tests/probes/interface/CMakeLists.txt b/tests/probes/interface/CMakeLists.txt index d6072935fa2..2912a212245 100644 --- a/tests/probes/interface/CMakeLists.txt +++ b/tests/probes/interface/CMakeLists.txt @@ -1,4 +1,4 @@ if(ENABLE_PROBES_UNIX) add_oscap_test_executable(test_probes_interface "test_probes_interface.c") - add_oscap_test("test_probes_interface.sh") + add_oscap_test("test_probes_interface.sh" LABELS unix) endif() diff --git a/tests/probes/isainfo/CMakeLists.txt b/tests/probes/isainfo/CMakeLists.txt index c551b975bde..9c6a79346ec 100644 --- a/tests/probes/isainfo/CMakeLists.txt +++ b/tests/probes/isainfo/CMakeLists.txt @@ -1,3 +1,3 @@ if(ENABLE_PROBES_SOLARIS) - add_oscap_test("test_probes_isainfo.sh") + add_oscap_test("test_probes_isainfo.sh" LABELS solaris) endif() diff --git a/tests/probes/maskattr/CMakeLists.txt b/tests/probes/maskattr/CMakeLists.txt index 4fa7c05e8e0..f1ef329cc1f 100644 --- a/tests/probes/maskattr/CMakeLists.txt +++ b/tests/probes/maskattr/CMakeLists.txt @@ -1,5 +1,5 @@ if(ENABLE_PROBES_INDEPENDENT) - add_oscap_test("test_object_entity_mask.sh") - add_oscap_test("test_object_entity_mask_oval_5_9.sh") - add_oscap_test("test_object_entity_nomask.sh") + add_oscap_test("test_object_entity_mask.sh" LABELS independent) + add_oscap_test("test_object_entity_mask_oval_5_9.sh" LABELS independent) + add_oscap_test("test_object_entity_nomask.sh" LABELS independent) endif() diff --git a/tests/probes/partition/CMakeLists.txt b/tests/probes/partition/CMakeLists.txt index e4a6c9e263b..0d255af36ad 100644 --- a/tests/probes/partition/CMakeLists.txt +++ b/tests/probes/partition/CMakeLists.txt @@ -1,4 +1,4 @@ if(ENABLE_PROBES_LINUX) - add_oscap_test("test_probes_partition.sh") - add_oscap_test("test_probes_partition_offline_mode.sh") + add_oscap_test("test_probes_partition.sh" LABELS linux linux_only) + add_oscap_test("test_probes_partition_offline_mode.sh" LABELS linux linux_only) endif() diff --git a/tests/probes/password/CMakeLists.txt b/tests/probes/password/CMakeLists.txt index 5c974eaf1e8..47db083377e 100644 --- a/tests/probes/password/CMakeLists.txt +++ b/tests/probes/password/CMakeLists.txt @@ -1,4 +1,5 @@ if(ENABLE_PROBES_UNIX) - add_oscap_test("test_probes_password.sh") - add_oscap_test("test_probes_password_offline.sh") + add_oscap_test("test_probes_password.sh" LABELS unix freebsd macos) + add_oscap_test("test_probes_password_offline.sh" LABELS unix macos) + add_oscap_test("test_probes_password_offline_fallback.sh" LABELS unix macos) endif() diff --git a/tests/probes/password/test_probes_password.xml.sh b/tests/probes/password/test_probes_password.xml.sh index 2a630d4b933..3af3d0be6f9 100644 --- a/tests/probes/password/test_probes_password.xml.sh +++ b/tests/probes/password/test_probes_password.xml.sh @@ -1,51 +1,84 @@ #!/usr/bin/env bash -passwd_file=`mktemp` +passwd_file=$(mktemp) +readonly AWK_PRINT_FIRST_FIELD='{print $1}' # getpwent returns duplicate entries for root and nobody users # due to a bug in systemd-userdb.service that occurs # in systemd 245 # https://github.com/systemd/systemd/issues/15160 -if which systemctl &>/dev/null && \ - [[ `systemctl --version | grep "systemd 245"` =~ "245" ]] ; then +if command -v systemctl >/dev/null 2>&1 && \ + systemctl --version | grep -q "systemd 245" ; then grep -Ev '^(root|nobody)' /etc/passwd > "$passwd_file" else - case `uname` in - # The first two lines of /etc/passwd on FreeBSD are not account entries - FreeBSD) - sed 1,2d /etc/passwd > $passwd_file + case "$(uname)" in + # BSD passwd files may contain comments that are ignored by getpwent(3). + Darwin|FreeBSD) + grep -Ev '^(#|$)' /etc/passwd > "$passwd_file" ;; + # Fall back to the raw passwd file elsewhere. *) cp /etc/passwd "$passwd_file" ;; esac fi -LINES_COUNT=`cat "$passwd_file" | wc -l` +LINES_COUNT=$(wc -l < "$passwd_file") function getField { - LINE=`sed -n "${I}p" "$passwd_file"` - case $1 in + local field_name="$1" + local line_number="$2" + local line + local username + + line=$(sed -n "${line_number}p" "$passwd_file") + username=$(echo "$line" | awk -F':' "$AWK_PRINT_FIRST_FIELD") + + case "$field_name" in 'username' ) - echo $LINE | awk -F':' '{print $1}' + echo "$username" ;; 'password' ) - echo $LINE | awk -F':' '{print $2}' + echo "$line" | awk -F':' '{print $2}' ;; 'user_id' ) - echo $LINE | awk -F':' '{print $3}' + case "$(uname)" in + FreeBSD) + id -u "$username" + ;; + Darwin) + id -u "$username" + ;; + *) + echo "$line" | awk -F':' '{print $3}' + ;; + esac ;; 'group_id' ) - echo $LINE | awk -F':' '{print $4}' + case "$(uname)" in + FreeBSD) + id -g "$username" + ;; + Darwin) + id -g "$username" + ;; + *) + echo "$line" | awk -F':' '{print $4}' + ;; + esac ;; 'gcos' ) - echo $LINE | awk -F':' '{gsub(/&/,"&",$5); print $5}' + echo "$line" | awk -F':' '{gsub(/&/,"&",$5); print $5}' ;; 'home_dir' ) - echo $LINE | awk -F':' '{print $6}' + echo "$line" | awk -F':' '{print $6}' ;; 'login_shell' ) - echo $LINE | awk -F':' '{print $7}' + echo "$line" | awk -F':' '{print $7}' + ;; + * ) + echo "Unexpected field: $field_name" >&2 + return 1 ;; esac } @@ -108,7 +141,7 @@ I=1 while [ $I -le $LINES_COUNT ]; do cat < - `getField "username" ${I}` + $(getField "username" "${I}") EOF I=$[$I+1] @@ -124,13 +157,13 @@ I=1 while [ $I -le $LINES_COUNT ]; do cat < - `getField 'username' $I` - `getField 'password' $I` - `getField 'user_id' $I` - `getField 'group_id' $I` - `getField 'gcos' $I` - `getField 'home_dir' $I` - `getField 'login_shell' $I` + $(getField 'username' "$I") + $(getField 'password' "$I") + $(getField 'user_id' "$I") + $(getField 'group_id' "$I") + $(getField 'gcos' "$I") + $(getField 'home_dir' "$I") + $(getField 'login_shell' "$I") EOF I=$[$I+1] diff --git a/tests/probes/password/test_probes_password_offline.sh b/tests/probes/password/test_probes_password_offline.sh index 0e4743448ed..ed2fac80cbc 100755 --- a/tests/probes/password/test_probes_password_offline.sh +++ b/tests/probes/password/test_probes_password_offline.sh @@ -32,12 +32,12 @@ function test_probes_password { tmpdir=$(mktemp -t -d "test_password.XXXXXX") mkdir -p "$tmpdir/etc" echo "root:x:0:0:root:/root:/bin/bash" > "$tmpdir/etc/passwd" - set_chroot_offline_test_mode "$tmpdir" + set_offline_chroot_dir "$tmpdir" $OSCAP oval eval --results $RF $DF - unset_chroot_offline_test_mode - rm -rf $tempdir + set_offline_chroot_dir "" + rm -rf "$tmpdir" if [ -f $RF ]; then result=$RF diff --git a/tests/probes/password/test_probes_password_offline_fallback.sh b/tests/probes/password/test_probes_password_offline_fallback.sh new file mode 100755 index 00000000000..11b7c31ee73 --- /dev/null +++ b/tests/probes/password/test_probes_password_offline_fallback.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +. $builddir/tests/test_common.sh + +set -e -o pipefail + +function test_probes_password_offline_fallback { + probecheck "password" || return 255 + + # This regression targets platforms without fgetpwent(3), primarily macOS. + case "$(uname)" in + Darwin) ;; + *) return 255 ;; + esac + + local definition_file="${srcdir}/test_probes_password_offline.xml" + local results_file="results.xml" + [[ -f "$results_file" ]] && rm -f "$results_file" + + tmpdir=$(mktemp -t -d "test_password_fallback.XXXXXX") + mkdir -p "$tmpdir/etc" + cat > "$tmpdir/etc/passwd" <<'EOF' +# comment line should be ignored + +invalid_line_without_separators +root:x:0:0:root:/root:/bin/bash +EOF + + set_offline_chroot_dir "$tmpdir" + $OSCAP oval eval --results "$results_file" "$definition_file" + set_offline_chroot_dir "" + rm -rf "$tmpdir" + + if [[ -f "$results_file" ]]; then + result="$results_file" + assert_exists 1 'oval_results/results/system/tests/test[@test_id="oval:1:tst:1"][@result="true"]' + else + return 1 + fi +} + +test_init + +test_run "test_probes_password_offline_fallback" test_probes_password_offline_fallback + +test_exit diff --git a/tests/probes/process58/CMakeLists.txt b/tests/probes/process58/CMakeLists.txt index 947665de652..9f4ea53f4c7 100644 --- a/tests/probes/process58/CMakeLists.txt +++ b/tests/probes/process58/CMakeLists.txt @@ -1,11 +1,11 @@ if(ENABLE_PROBES_UNIX) - add_oscap_test("capability.sh") - add_oscap_test("command_line.sh") - add_oscap_test("command_line_extra.sh") - add_oscap_test("dev_to_tty.sh") - add_oscap_test("empty_proc.sh") - add_oscap_test("loginuid.sh") - add_oscap_test("selinux_domain_label.sh") - add_oscap_test("sessionid.sh") - add_oscap_test("test_probes_process58_offline_mode.sh") + add_oscap_test("capability.sh" LABELS unix) + add_oscap_test("command_line.sh" LABELS unix) + add_oscap_test("command_line_extra.sh" LABELS unix) + add_oscap_test("dev_to_tty.sh" LABELS unix) + add_oscap_test("empty_proc.sh" LABELS unix) + add_oscap_test("loginuid.sh" LABELS unix) + add_oscap_test("selinux_domain_label.sh" LABELS unix) + add_oscap_test("sessionid.sh" LABELS unix) + add_oscap_test("test_probes_process58_offline_mode.sh" LABELS unix) endif() diff --git a/tests/probes/rpm/rpminfo/CMakeLists.txt b/tests/probes/rpm/rpminfo/CMakeLists.txt index 9f1b7a4c107..1c03b4dc568 100644 --- a/tests/probes/rpm/rpminfo/CMakeLists.txt +++ b/tests/probes/rpm/rpminfo/CMakeLists.txt @@ -1,4 +1,4 @@ if(ENABLE_PROBES_LINUX) - add_oscap_test("test_probes_rpminfo.sh") - add_oscap_test("test_probes_rpminfo_offline.sh") + add_oscap_test("test_probes_rpminfo.sh" LABELS linux linux_only) + add_oscap_test("test_probes_rpminfo_offline.sh" LABELS linux linux_only) endif() diff --git a/tests/probes/rpm/rpmverify/CMakeLists.txt b/tests/probes/rpm/rpmverify/CMakeLists.txt index e7289d2c82c..c8dc0abbb37 100644 --- a/tests/probes/rpm/rpmverify/CMakeLists.txt +++ b/tests/probes/rpm/rpmverify/CMakeLists.txt @@ -1,4 +1,4 @@ if(ENABLE_PROBES_LINUX) - add_oscap_test("test_probes_rpmverify_not_equals_operation.sh") - add_oscap_test("test_probes_rpmverify_not_equals_operation_offline.sh") + add_oscap_test("test_probes_rpmverify_not_equals_operation.sh" LABELS linux linux_only) + add_oscap_test("test_probes_rpmverify_not_equals_operation_offline.sh" LABELS linux linux_only) endif() diff --git a/tests/probes/rpm/rpmverifyfile/CMakeLists.txt b/tests/probes/rpm/rpmverifyfile/CMakeLists.txt index 0a8f71f29cc..18380cd9b4e 100644 --- a/tests/probes/rpm/rpmverifyfile/CMakeLists.txt +++ b/tests/probes/rpm/rpmverifyfile/CMakeLists.txt @@ -1,5 +1,5 @@ if(ENABLE_PROBES_LINUX) - add_oscap_test("test_probes_rpmverifyfile.sh") - add_oscap_test("test_probes_rpmverifyfile_older.sh") - add_oscap_test("test_probes_rpmverifyfile_offline.sh") + add_oscap_test("test_probes_rpmverifyfile.sh" LABELS linux linux_only) + add_oscap_test("test_probes_rpmverifyfile_older.sh" LABELS linux linux_only) + add_oscap_test("test_probes_rpmverifyfile_offline.sh" LABELS linux linux_only) endif() diff --git a/tests/probes/rpm/rpmverifypackage/CMakeLists.txt b/tests/probes/rpm/rpmverifypackage/CMakeLists.txt index 9a4205d3bb3..47673f24993 100644 --- a/tests/probes/rpm/rpmverifypackage/CMakeLists.txt +++ b/tests/probes/rpm/rpmverifypackage/CMakeLists.txt @@ -1,4 +1,4 @@ if(ENABLE_PROBES_LINUX) - add_oscap_test("test_probes_rpmverifypackage.sh") - add_oscap_test("test_probes_rpmverifypackage_offline.sh") + add_oscap_test("test_probes_rpmverifypackage.sh" LABELS linux linux_only) + add_oscap_test("test_probes_rpmverifypackage_offline.sh" LABELS linux linux_only) endif() diff --git a/tests/probes/runlevel/CMakeLists.txt b/tests/probes/runlevel/CMakeLists.txt index 582960d06b2..3e81067d795 100644 --- a/tests/probes/runlevel/CMakeLists.txt +++ b/tests/probes/runlevel/CMakeLists.txt @@ -1,3 +1,4 @@ if(ENABLE_PROBES_UNIX) - add_oscap_test("test_probes_runlevel.sh") + add_oscap_test("test_probes_runlevel.sh" LABELS unix linux_only) + add_oscap_test("test_probes_runlevel_unsupported.sh" LABELS unix macos freebsd) endif() diff --git a/tests/probes/runlevel/test_probes_runlevel_unsupported.sh b/tests/probes/runlevel/test_probes_runlevel_unsupported.sh new file mode 100755 index 00000000000..7c2e1aa3c9b --- /dev/null +++ b/tests/probes/runlevel/test_probes_runlevel_unsupported.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +. $builddir/tests/test_common.sh + +set -e -o pipefail + +function test_probes_runlevel_unsupported { + probecheck "runlevel" || return 255 + + case "$(uname)" in + Darwin|FreeBSD) ;; + *) return 255 ;; + esac + + local definition="${top_srcdir}/tests/oval_details/runlevel.oval.xml" + local results="results_unsupported.xml" + [[ -f "$results" ]] && rm -f "$results" + + $OSCAP oval eval --results "$results" "$definition" + + result="$results" + assert_exists 1 'oval_results/results/system/definitions/definition[@definition_id="oval:x:def:1"][@result="error"]' + assert_exists 1 'oval_results/results/system/tests/test[@test_id="oval:x:tst:1"][@result="error"]' + assert_exists 1 'oval_results/results/system/oval_system_characteristics/collected_objects/object[@id="oval:ssg:obj:1313"][@flag="error"]' + assert_exists 1 'oval_results/results/system/oval_system_characteristics/collected_objects/object/message[text()="get_runlevel failed."]' +} + +test_init + +test_run "test_probes_runlevel_unsupported" test_probes_runlevel_unsupported + +test_exit diff --git a/tests/probes/selinuxboolean/CMakeLists.txt b/tests/probes/selinuxboolean/CMakeLists.txt index 6a810d40182..fe6b3a0e48c 100644 --- a/tests/probes/selinuxboolean/CMakeLists.txt +++ b/tests/probes/selinuxboolean/CMakeLists.txt @@ -1,4 +1,4 @@ if(ENABLE_PROBES_LINUX) - add_oscap_test("test_probes_selinuxboolean.sh") - add_oscap_test("test_probes_selinuxboolean_offline_mode.sh") + add_oscap_test("test_probes_selinuxboolean.sh" LABELS linux linux_only) + add_oscap_test("test_probes_selinuxboolean_offline_mode.sh" LABELS linux linux_only) endif() diff --git a/tests/probes/selinuxsecuritycontext/CMakeLists.txt b/tests/probes/selinuxsecuritycontext/CMakeLists.txt index 12ab74f52df..29e29fac362 100644 --- a/tests/probes/selinuxsecuritycontext/CMakeLists.txt +++ b/tests/probes/selinuxsecuritycontext/CMakeLists.txt @@ -1,4 +1,4 @@ if(ENABLE_PROBES_LINUX) - add_oscap_test("test_probes_selinuxsecuritycontext.sh") - add_oscap_test("test_probes_selinuxsecuritycontext_offline_mode.sh") + add_oscap_test("test_probes_selinuxsecuritycontext.sh" LABELS linux linux_only) + add_oscap_test("test_probes_selinuxsecuritycontext_offline_mode.sh" LABELS linux linux_only) endif() diff --git a/tests/probes/shadow/CMakeLists.txt b/tests/probes/shadow/CMakeLists.txt index c461ce7dbcb..1531708041e 100644 --- a/tests/probes/shadow/CMakeLists.txt +++ b/tests/probes/shadow/CMakeLists.txt @@ -1,4 +1,5 @@ if(ENABLE_PROBES_UNIX) - add_oscap_test("test_probes_shadow.sh") - add_oscap_test("test_probes_shadow_offline.sh") + add_oscap_test("test_probes_shadow.sh" LABELS unix) + add_oscap_test("test_probes_shadow_offline.sh" LABELS unix) + add_oscap_test("test_probes_shadow_offline_unsupported.sh" LABELS unix macos) endif() diff --git a/tests/probes/shadow/test_probes_shadow_offline_unsupported.sh b/tests/probes/shadow/test_probes_shadow_offline_unsupported.sh new file mode 100755 index 00000000000..37d29724189 --- /dev/null +++ b/tests/probes/shadow/test_probes_shadow_offline_unsupported.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +. $builddir/tests/test_common.sh + +set -e -o pipefail + +function test_probes_shadow_offline_unsupported { + probecheck "shadow" || return 255 + + case "$(uname)" in + Darwin) ;; + *) return 255 ;; + esac + + local definition="${srcdir}/test_probes_shadow_offline.xml" + local results="results_unsupported.xml" + [[ -f "$results" ]] && rm -f "$results" + + tmpdir=$(make_temp_dir /tmp "test_offline_mode_shadow_unsupported") + mkdir -p "${tmpdir}/etc" + echo "root:!locked::0:99999:7:::" > "${tmpdir}/etc/shadow" + set_offline_chroot_dir "${tmpdir}" + + $OSCAP oval eval --results "$results" "$definition" + + set_offline_chroot_dir "" + rm -rf "${tmpdir}" + + result="$results" + assert_exists 1 'oval_results/results/system/definitions/definition[@definition_id="oval:1:def:1"][@result="not applicable"]' + assert_exists 1 'oval_results/results/system/tests/test[@test_id="oval:1:tst:1"][@result="not applicable"]' + assert_exists 1 'oval_results/results/system/oval_system_characteristics/collected_objects/object[@id="oval:1:obj:1"][@flag="not applicable"]' +} + +test_init + +test_run "test_probes_shadow_offline_unsupported" test_probes_shadow_offline_unsupported + +test_exit diff --git a/tests/probes/sql57/CMakeLists.txt b/tests/probes/sql57/CMakeLists.txt index 787096c2b13..20d41212475 100644 --- a/tests/probes/sql57/CMakeLists.txt +++ b/tests/probes/sql57/CMakeLists.txt @@ -1,3 +1,3 @@ if(ENABLE_PROBES_INDEPENDENT AND TARGET sql57_probe) - add_oscap_test("unsupported_engine.sh") + add_oscap_test("unsupported_engine.sh" LABELS independent) endif() diff --git a/tests/probes/symlink/CMakeLists.txt b/tests/probes/symlink/CMakeLists.txt index 4ebd5c363db..25b75198493 100644 --- a/tests/probes/symlink/CMakeLists.txt +++ b/tests/probes/symlink/CMakeLists.txt @@ -1,4 +1,4 @@ if(ENABLE_PROBES_UNIX) - add_oscap_test("test_probes_symlink.sh") - add_oscap_test("test_offline_mode_symlink.sh") + add_oscap_test("test_probes_symlink.sh" LABELS unix) + add_oscap_test("test_offline_mode_symlink.sh" LABELS unix) endif() diff --git a/tests/probes/sysctl/CMakeLists.txt b/tests/probes/sysctl/CMakeLists.txt index a5ede2600e5..5afc25c9610 100644 --- a/tests/probes/sysctl/CMakeLists.txt +++ b/tests/probes/sysctl/CMakeLists.txt @@ -1,5 +1,5 @@ if(ENABLE_PROBES_UNIX) - add_oscap_test("test_sysctl_probe.sh") - add_oscap_test("test_sysctl_probe_all.sh") - add_oscap_test("test_sysctl_probe_offline_mode.sh") + add_oscap_test("test_sysctl_probe.sh" LABELS unix freebsd macos) + add_oscap_test("test_sysctl_probe_all.sh" LABELS unix freebsd macos linux) + add_oscap_test("test_sysctl_probe_offline_mode.sh" LABELS unix) endif() diff --git a/tests/probes/sysctl/test_sysctl_probe.sh b/tests/probes/sysctl/test_sysctl_probe.sh index 395fb964d81..1ab20a8ddb2 100755 --- a/tests/probes/sysctl/test_sysctl_probe.sh +++ b/tests/probes/sysctl/test_sysctl_probe.sh @@ -14,6 +14,9 @@ function perform_test { FreeBSD) $OSCAP oval eval --results $result $srcdir/test_sysctl_probe_freebsd.oval.xml 2>$stderr ;; + Darwin) + $OSCAP oval eval --results $result $srcdir/test_sysctl_probe_freebsd.oval.xml 2>$stderr + ;; *) $OSCAP oval eval --results $result $srcdir/test_sysctl_probe.oval.xml 2>$stderr ;; @@ -25,6 +28,10 @@ function perform_test { assert_exists 1 "/oval_results/results/system/oval_system_characteristics/system_data/unix-sys:sysctl_item/unix-sys:name[text()='kern.hostname']" assert_exists 1 "/oval_results/results/system/oval_system_characteristics/system_data/unix-sys:sysctl_item/unix-sys:value[text()='$hostname']" ;; + Darwin) + assert_exists 1 "/oval_results/results/system/oval_system_characteristics/system_data/unix-sys:sysctl_item/unix-sys:name[text()='kern.hostname']" + assert_exists 1 "/oval_results/results/system/oval_system_characteristics/system_data/unix-sys:sysctl_item/unix-sys:value[text()='$hostname']" + ;; *) assert_exists 1 "/oval_results/results/system/oval_system_characteristics/system_data/unix-sys:sysctl_item/unix-sys:name[text()='kernel.hostname']" assert_exists 1 "/oval_results/results/system/oval_system_characteristics/system_data/unix-sys:sysctl_item/unix-sys:value[text()='$hostname']" diff --git a/tests/probes/sysctl/test_sysctl_probe_all.sh b/tests/probes/sysctl/test_sysctl_probe_all.sh index efaa31b9b1c..348f3d6b9b7 100755 --- a/tests/probes/sysctl/test_sysctl_probe_all.sh +++ b/tests/probes/sysctl/test_sysctl_probe_all.sh @@ -10,6 +10,13 @@ PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin function perform_test { probecheck "sysctl" || return 255 + case $(uname) in + Darwin) + # macOS exposes many implementation-specific sysctls; this strict parity + # test is intended for Linux/FreeBSD naming behavior. + return 255 + ;; + esac name=$(basename $0 .sh) @@ -27,7 +34,16 @@ function perform_test { case $(uname) in FreeBSD) - sysctl -aN 2> /dev/null > "$sysctlNames" + sysctl -ae 2> /dev/null | \ + grep -E '^[[:alpha:]][[:alnum:]_.%-]*=' | \ + cut -d "=" -f 1 | \ + sort -u > "$sysctlNames" + ;; + Darwin) + sysctl -ae 2> /dev/null | \ + grep -E '^[[:alpha:]][[:alnum:]_.%-]*=' | \ + cut -d "=" -f 1 | \ + sort -u > "$sysctlNames" ;; Linux) # sysctl has duplicities in output @@ -35,23 +51,39 @@ function perform_test { # kernel parameters might use "/" and "." separators interchangeably - normalizing sysctl -a --deprecated 2> /dev/null | tr "/" "." | cut -d "=" -f 1 | tr -d " " | sort -u > "$sysctlNames" ;; + *) + return 255 + ;; esac - grep unix-sys:name "$result" | xsed -E 's;.*>(.*)<.*;\1;g' | sort > "$ourNames" + # Some platforms emit large values (for example FreeBSD's kern.geom.confxml) + # on the same line as the element. Match the explicit name + # tags so value payloads do not pollute the extracted key list. + grep '' "$result" | \ + sed -E 's;.*([^<]+).*;\1;g' | \ + sort > "$ourNames" echo "Diff (sysctlNames / ourNames): ------" diff "$sysctlNames" "$ourNames" echo "-------------------------------------" - # remove oscap error message related to permissions from stderr - sed -i -E "/^E: oscap: +Can't read sysctl value from /d" "$stderr" - sed -i -E "/^E: oscap: +An error.*, Operation not permitted/d" "$stderr" + # remove known, non-fatal errors from stderr in a portable way (BSD/GNU sed) + tmp_filtered=$(mktemp ${name}.stderr.filtered.XXXXXX) + sed -E "/^E: oscap: +Can't read sysctl value from /d" "$stderr" > "$tmp_filtered" + mv "$tmp_filtered" "$stderr" + tmp_filtered=$(mktemp ${name}.stderr.filtered.XXXXXX) + sed -E "/^E: oscap: +An error.*, Operation not permitted/d" "$stderr" > "$tmp_filtered" + mv "$tmp_filtered" "$stderr" # remove oscap error message related to gibberish binary entries # that can't fit into 8K buffer and result in errno 14 # (for example /proc/sys/kernel/spl/hostid could be the case) - sed -i -E "/^E: oscap: +An error.*14, Bad address/d" "$stderr" - sed -i "/^.*hugepages.*$/d" "$stderr" + tmp_filtered=$(mktemp ${name}.stderr.filtered.XXXXXX) + sed -E "/^E: oscap: +An error.*14, Bad address/d" "$stderr" > "$tmp_filtered" + mv "$tmp_filtered" "$stderr" + tmp_filtered=$(mktemp ${name}.stderr.filtered.XXXXXX) + sed "/^.*hugepages.*$/d" "$stderr" > "$tmp_filtered" + mv "$tmp_filtered" "$stderr" echo "Errors (without messages related to permissions):" cat "$stderr" diff --git a/tests/probes/system_info/CMakeLists.txt b/tests/probes/system_info/CMakeLists.txt index f5b12c3435b..19b28d36e4f 100644 --- a/tests/probes/system_info/CMakeLists.txt +++ b/tests/probes/system_info/CMakeLists.txt @@ -1,5 +1,5 @@ if(ENABLE_PROBES_INDEPENDENT) add_oscap_test_executable(test_probes_system_info "test_probes_system_info.c") - add_oscap_test("test_probes_system_info.sh") - add_oscap_test("test_probes_system_info_offline_mode.sh") + add_oscap_test("test_probes_system_info.sh" LABELS independent) + add_oscap_test("test_probes_system_info_offline_mode.sh" LABELS independent) endif() diff --git a/tests/probes/systemdunitdependency/CMakeLists.txt b/tests/probes/systemdunitdependency/CMakeLists.txt index c60a47e88f8..55f62bb93bb 100644 --- a/tests/probes/systemdunitdependency/CMakeLists.txt +++ b/tests/probes/systemdunitdependency/CMakeLists.txt @@ -1,7 +1,7 @@ if(ENABLE_PROBES_LINUX) if(DBUS_FOUND) - add_oscap_test("test_probes_systemdunitdependency.sh") - add_oscap_test("test_probes_systemdunitdependency_validation.sh") - add_oscap_test("test_probes_systemdunitdependency_offline_mode.sh") + add_oscap_test("test_probes_systemdunitdependency.sh" LABELS linux linux_only) + add_oscap_test("test_probes_systemdunitdependency_validation.sh" LABELS linux linux_only) + add_oscap_test("test_probes_systemdunitdependency_offline_mode.sh" LABELS linux linux_only) endif() endif() diff --git a/tests/probes/systemdunitproperty/CMakeLists.txt b/tests/probes/systemdunitproperty/CMakeLists.txt index 521d6ece66e..daa5e95eeb4 100644 --- a/tests/probes/systemdunitproperty/CMakeLists.txt +++ b/tests/probes/systemdunitproperty/CMakeLists.txt @@ -1,7 +1,7 @@ if(ENABLE_PROBES_LINUX) if(DBUS_FOUND) - add_oscap_test("test_probes_systemdunitproperty.sh") - add_oscap_test("test_probes_systemdunitproperty_mount_wants.sh") - add_oscap_test("test_probes_systemdunitproperty_offline_mode.sh") + add_oscap_test("test_probes_systemdunitproperty.sh" LABELS linux linux_only) + add_oscap_test("test_probes_systemdunitproperty_mount_wants.sh" LABELS linux linux_only) + add_oscap_test("test_probes_systemdunitproperty_offline_mode.sh" LABELS linux linux_only) endif() endif() diff --git a/tests/probes/textfilecontent54/CMakeLists.txt b/tests/probes/textfilecontent54/CMakeLists.txt index 3f95ae8a20e..082d50127b1 100644 --- a/tests/probes/textfilecontent54/CMakeLists.txt +++ b/tests/probes/textfilecontent54/CMakeLists.txt @@ -1,10 +1,10 @@ if(ENABLE_PROBES_INDEPENDENT) - add_oscap_test("test_behavior_multiline.sh") - add_oscap_test("test_filecontent_non_utf.sh") - add_oscap_test("test_offline_mode_textfilecontent54.sh") - add_oscap_test("test_probes_textfilecontent54.sh") - add_oscap_test("test_recursion_limit.sh") - add_oscap_test("test_symlinks.sh") - add_oscap_test("test_validation_of_various_oval_versions.sh") - add_oscap_test("test_negative_instance.sh") + add_oscap_test("test_behavior_multiline.sh" LABELS independent) + add_oscap_test("test_filecontent_non_utf.sh" LABELS independent) + add_oscap_test("test_offline_mode_textfilecontent54.sh" LABELS independent) + add_oscap_test("test_probes_textfilecontent54.sh" LABELS independent) + add_oscap_test("test_recursion_limit.sh" LABELS independent) + add_oscap_test("test_symlinks.sh" LABELS independent) + add_oscap_test("test_validation_of_various_oval_versions.sh" LABELS independent) + add_oscap_test("test_negative_instance.sh" LABELS independent) endif() diff --git a/tests/probes/uname/CMakeLists.txt b/tests/probes/uname/CMakeLists.txt index e287eb95a5e..342358c4c70 100644 --- a/tests/probes/uname/CMakeLists.txt +++ b/tests/probes/uname/CMakeLists.txt @@ -1,3 +1,3 @@ if(ENABLE_PROBES_UNIX) - add_oscap_test("test_probes_uname.sh") + add_oscap_test("test_probes_uname.sh" LABELS unix) endif() diff --git a/tests/probes/xinetd/CMakeLists.txt b/tests/probes/xinetd/CMakeLists.txt index f1bbbbc8bf2..abf742688a2 100644 --- a/tests/probes/xinetd/CMakeLists.txt +++ b/tests/probes/xinetd/CMakeLists.txt @@ -13,5 +13,5 @@ if(ENABLE_PROBES_UNIX) "${CMAKE_SOURCE_DIR}/src/OVAL/probes/SEAP/generic/rbt" "${CMAKE_SOURCE_DIR}/src/common" ) - add_oscap_test("test_xinetd_probe.sh") + add_oscap_test("test_xinetd_probe.sh" LABELS unix) endif() diff --git a/tests/probes/xmlfilecontent/CMakeLists.txt b/tests/probes/xmlfilecontent/CMakeLists.txt index 6318f769894..ff78a44bd3a 100644 --- a/tests/probes/xmlfilecontent/CMakeLists.txt +++ b/tests/probes/xmlfilecontent/CMakeLists.txt @@ -1,3 +1,3 @@ if(OPENSCAP_PROBE_INDEPENDENT_XMLFILECONTENT) - add_oscap_test("test_xmlfilecontent_probe.sh") + add_oscap_test("test_xmlfilecontent_probe.sh" LABELS independent) endif() \ No newline at end of file diff --git a/tests/probes/yamlfilecontent/CMakeLists.txt b/tests/probes/yamlfilecontent/CMakeLists.txt index a08114cba8a..f0ccc9fe6b0 100644 --- a/tests/probes/yamlfilecontent/CMakeLists.txt +++ b/tests/probes/yamlfilecontent/CMakeLists.txt @@ -1,8 +1,8 @@ if(ENABLE_PROBES_INDEPENDENT) - add_oscap_test("test_probes_yamlfilecontent_key.sh") - add_oscap_test("test_probes_yamlfilecontent_array.sh") - add_oscap_test("test_probes_yamlfilecontent_offline_mode.sh") - add_oscap_test("test_probes_yamlfilecontent_types.sh") - add_oscap_test("test_probes_yamlfilecontent_content.sh") + add_oscap_test("test_probes_yamlfilecontent_key.sh" LABELS independent) + add_oscap_test("test_probes_yamlfilecontent_array.sh" LABELS independent) + add_oscap_test("test_probes_yamlfilecontent_offline_mode.sh" LABELS independent) + add_oscap_test("test_probes_yamlfilecontent_types.sh" LABELS independent) + add_oscap_test("test_probes_yamlfilecontent_content.sh" LABELS independent) endif() diff --git a/tests/test_common.sh.in b/tests/test_common.sh.in index e055805d7d8..39022e56df0 100755 --- a/tests/test_common.sh.in +++ b/tests/test_common.sh.in @@ -339,7 +339,35 @@ make_temp_file() { xsed() { case $(uname) in FreeBSD) - gsed "$@" + if command -v gsed >/dev/null 2>&1; then + gsed "$@" + return + fi + + local inplace=0 + local arg + local transformed=() + + for arg in "$@"; do + if [ "$arg" = "-i" ]; then + inplace=1 + continue + fi + + # Translate the small subset of GNU sed escapes used by the test + # suite so the fallback works with FreeBSD sed too. + arg=${arg//\\s/[[:space:]]} + arg=${arg//\\S/[^[:space:]]} + arg=${arg//\\w/[[:alnum:]_]} + arg=${arg//\\W/[^[:alnum:]_]} + transformed+=("$arg") + done + + if [ $inplace -eq 1 ]; then + sed -i '' "${transformed[@]}" + else + sed "${transformed[@]}" + fi ;; *) sed "$@" diff --git a/tests/utils/CMakeLists.txt b/tests/utils/CMakeLists.txt index 74438356145..a19b7b00516 100644 --- a/tests/utils/CMakeLists.txt +++ b/tests/utils/CMakeLists.txt @@ -3,8 +3,8 @@ add_oscap_test("test_utils_args.sh") add_oscap_test("test_verbose_options.sh") if(PY_PYTEST) - add_test( - NAME "autotailor-unit-tests" + add_oscap_ctest("autotailor-unit-tests" COMMAND ${PYTHON_EXECUTABLE} -m pytest -v "${CMAKE_CURRENT_SOURCE_DIR}/test_autotailor.py" + LABELS python ) endif()