From 77ec3356e9a79061ffb2ff9bef20b4bedb55a10a Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Wed, 11 Mar 2026 13:14:21 -0700 Subject: [PATCH 01/25] Improve macOS and FreeBSD portability in build and probes Guard Linux-specific compiler/linker assumptions in CMake and add platform-compatible fallbacks in Unix probes and common utilities. Add Darwin handling for sysctl collection, memory usage, and MAC address resolution. Add unsupported-path behavior for runlevel on platforms where runlevels are not applicable. Handle missing fgetpwent environments in password/shadow probe paths so builds complete and offline evaluation remains functional. --- CMakeLists.txt | 13 +++++- config.h.in | 1 + src/OVAL/probes/unix/password_probe.c | 45 +++++++++++++++++++ src/OVAL/probes/unix/runlevel_probe.c | 3 +- src/OVAL/probes/unix/shadow_probe.c | 5 +++ src/OVAL/probes/unix/sysctl_probe.c | 12 +++++- src/XCCDF/result.c | 62 +++++++++++++++++++++++++++ src/common/memusage.c | 52 +++++++++++++++++++++- 8 files changed, 188 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ca43d1c009..3efc6efc071 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -205,6 +205,7 @@ check_function_exists(memalign HAVE_MEMALIGN) check_function_exists(fts_open HAVE_FTS_OPEN) check_function_exists(strsep HAVE_STRSEP) check_function_exists(strptime HAVE_STRPTIME) +check_function_exists(fgetpwent HAVE_FGETPWENT) check_include_file(syslog.h HAVE_SYSLOG_H) check_include_file(stdio_ext.h HAVE_STDIO_EXT_H) @@ -529,8 +530,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/config.h.in b/config.h.in index 305bd231cfb..4cf7f638d43 100644 --- a/config.h.in +++ b/config.h.in @@ -85,6 +85,7 @@ #cmakedefine HAVE_STRSEP #cmakedefine HAVE_FLOCK #cmakedefine HAVE_STRPTIME +#cmakedefine HAVE_FGETPWENT #cmakedefine OPENSCAP_PROBE_INDEPENDENT_ENVIRONMENTVARIABLE #cmakedefine OPENSCAP_PROBE_INDEPENDENT_ENVIRONMENTVARIABLE58 diff --git a/src/OVAL/probes/unix/password_probe.c b/src/OVAL/probes/unix/password_probe.c index 0d9bb2923f2..8f4d6084b2a 100644 --- a/src/OVAL/probes/unix/password_probe.c +++ b/src/OVAL/probes/unix/password_probe.c @@ -67,6 +67,51 @@ #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. + */ +#ifndef HAVE_FGETPWENT +static struct passwd *oscap_fgetpwent(FILE *fp) +{ + static char line[2048]; + static struct passwd pw; + char *fields[7], *p; + int f; + + while (fgets(line, sizeof(line), fp) != NULL) { + if (line[0] == '#' || line[0] == '\n') + continue; + f = 0; + p = line; + while (f < 7) { + fields[f++] = p; + p = strchr(p, ':'); + if (p) + *p++ = '\0'; + else + break; + } + if (f < 7) + continue; + /* strip trailing newline from shell field */ + size_t n = strlen(fields[6]); + if (n > 0 && fields[6][n - 1] == '\n') + fields[6][n - 1] = '\0'; + pw.pw_name = fields[0]; + pw.pw_passwd = fields[1]; + pw.pw_uid = (uid_t)atoi(fields[2]); + pw.pw_gid = (gid_t)atoi(fields[3]); + 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..79fc3543ef3 100644 --- a/src/OVAL/probes/unix/sysctl_probe.c +++ b/src/OVAL/probes/unix/sysctl_probe.c @@ -44,6 +44,16 @@ #include "common/debug_priv.h" #define SYSCTL_CMD "/sbin/sysctl -ae" +#elif defined(OS_APPLE) +#include +#include +#include +#include + +#include "common/debug_priv.h" + +/* On macOS sysctl(8) lives under /usr/sbin */ +#define SYSCTL_CMD "/usr/sbin/sysctl -ae" #endif #if defined(OS_LINUX) @@ -305,7 +315,7 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) return (0); } -#elif defined(OS_FREEBSD) +#elif defined(OS_FREEBSD) || defined(OS_APPLE) int sysctl_probe_offline_mode_supported(void) { 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/common/memusage.c b/src/common/memusage.c index f6baca32983..df30ee6dfd3 100644 --- a/src/common/memusage.c +++ b/src/common/memusage.c @@ -50,7 +50,13 @@ #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" @@ -305,6 +311,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 +358,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 = info.resident_size / 1024; + mu->mu_data = info.virtual_size / 1024; + /* 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; From e4ebf21d8ec0eb2a11e89479f96a427bd26e0496 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Wed, 11 Mar 2026 13:14:26 -0700 Subject: [PATCH 02/25] Add CTest label support and direct-test registration helper Extend test registration helpers to accept LABELS and auto-derive suite labels for easier selective execution across platforms. Introduce a helper for direct CTest command registration so non-shell tests follow the same labeling conventions as shell-driven tests. --- tests/API/probes/CMakeLists.txt | 17 +++++-- tests/CMakeLists.txt | 51 +++++++++++++++++++ .../probes/environmentvariable/CMakeLists.txt | 2 +- .../environmentvariable58/CMakeLists.txt | 4 +- tests/probes/family/CMakeLists.txt | 2 +- tests/probes/file/CMakeLists.txt | 6 +-- .../fileextendedattribute/CMakeLists.txt | 2 +- tests/probes/filehash/CMakeLists.txt | 2 +- tests/probes/filehash58/CMakeLists.txt | 4 +- tests/probes/filemd5/CMakeLists.txt | 2 +- tests/probes/fwupdsecattr/CMakeLists.txt | 4 +- tests/probes/iflisteners/CMakeLists.txt | 2 +- tests/probes/interface/CMakeLists.txt | 2 +- tests/probes/isainfo/CMakeLists.txt | 2 +- tests/probes/maskattr/CMakeLists.txt | 6 +-- tests/probes/partition/CMakeLists.txt | 4 +- tests/probes/password/CMakeLists.txt | 5 +- tests/probes/process58/CMakeLists.txt | 18 +++---- tests/probes/runlevel/CMakeLists.txt | 2 +- tests/probes/selinuxboolean/CMakeLists.txt | 4 +- .../selinuxsecuritycontext/CMakeLists.txt | 4 +- tests/probes/shadow/CMakeLists.txt | 4 +- tests/probes/sql57/CMakeLists.txt | 2 +- tests/probes/symlink/CMakeLists.txt | 4 +- tests/probes/sysctl/CMakeLists.txt | 6 +-- tests/probes/system_info/CMakeLists.txt | 4 +- .../systemdunitdependency/CMakeLists.txt | 6 +-- .../probes/systemdunitproperty/CMakeLists.txt | 6 +-- tests/probes/textfilecontent54/CMakeLists.txt | 16 +++--- tests/probes/uname/CMakeLists.txt | 2 +- tests/probes/xinetd/CMakeLists.txt | 2 +- tests/probes/xmlfilecontent/CMakeLists.txt | 2 +- tests/probes/yamlfilecontent/CMakeLists.txt | 10 ++-- tests/utils/CMakeLists.txt | 4 +- 34 files changed, 137 insertions(+), 76 deletions(-) diff --git a/tests/API/probes/CMakeLists.txt b/tests/API/probes/CMakeLists.txt index cd5ca8358d3..ce6197d98cf 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,13 @@ 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/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) 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..04c9b8e21db 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) + add_oscap_test("test_probes_password_offline_fallback.sh" LABELS unix macos) endif() 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/runlevel/CMakeLists.txt b/tests/probes/runlevel/CMakeLists.txt index 582960d06b2..aaaec1e1ccb 100644 --- a/tests/probes/runlevel/CMakeLists.txt +++ b/tests/probes/runlevel/CMakeLists.txt @@ -1,3 +1,3 @@ if(ENABLE_PROBES_UNIX) - add_oscap_test("test_probes_runlevel.sh") + add_oscap_test("test_probes_runlevel.sh" LABELS unix linux_only) endif() 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..f2769063401 100644 --- a/tests/probes/shadow/CMakeLists.txt +++ b/tests/probes/shadow/CMakeLists.txt @@ -1,4 +1,4 @@ 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) endif() 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/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/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() From 8e7735dc96e6b402435e65d1d311cb5be66aba0f Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Wed, 11 Mar 2026 13:14:33 -0700 Subject: [PATCH 03/25] Add targeted regressions for portability behavior Add memusage platform tests and password offline fallback coverage to protect recent cross-platform fixes. Update sysctl probe tests for Darwin-specific behavior and portable stderr handling so test outcomes are consistent on macOS. --- tests/API/probes/test_memusage_platform.c | 37 +++++++++++++++ tests/API/probes/test_memusage_platform.sh | 9 ++++ .../test_probes_password_offline_fallback.sh | 46 +++++++++++++++++++ tests/probes/sysctl/test_sysctl_probe.sh | 7 +++ tests/probes/sysctl/test_sysctl_probe_all.sh | 31 +++++++++++-- 5 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 tests/API/probes/test_memusage_platform.c create mode 100755 tests/API/probes/test_memusage_platform.sh create mode 100755 tests/probes/password/test_probes_password_offline_fallback.sh diff --git a/tests/API/probes/test_memusage_platform.c b/tests/API/probes/test_memusage_platform.c new file mode 100644 index 00000000000..0b38c43e68f --- /dev/null +++ b/tests/API/probes/test_memusage_platform.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "memusage.h" +#include "memusage.c" + +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..74cde71f3a9 --- /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/probes/password/test_probes_password_offline_fallback.sh b/tests/probes/password/test_probes_password_offline_fallback.sh new file mode 100755 index 00000000000..3b327295810 --- /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 DF="${srcdir}/test_probes_password_offline.xml" + local RF="results.xml" + [ -f "$RF" ] && rm -f "$RF" + + 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_chroot_offline_test_mode "$tmpdir" + $OSCAP oval eval --results "$RF" "$DF" + unset_chroot_offline_test_mode + rm -rf "$tmpdir" + + if [ -f "$RF" ]; then + result="$RF" + 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/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..6dcbefe8a23 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) @@ -29,12 +36,18 @@ function perform_test { FreeBSD) sysctl -aN 2> /dev/null > "$sysctlNames" ;; + Darwin) + sysctl -aN 2> /dev/null > "$sysctlNames" + ;; Linux) # sysctl has duplicities in output # hide permission errors like: "sysctl: permission denied on key 'fs.protected_hardlinks'" # 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" @@ -43,15 +56,23 @@ function perform_test { 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" From f6ec0c285ea013954312f8be9dca6966b10edfce Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Wed, 11 Mar 2026 13:14:38 -0700 Subject: [PATCH 04/25] Document cross-platform build and label-based test execution Update developer documentation with label-driven CTest usage, helper semantics, and practical guidance for building and testing on macOS and FreeBSD. Summarize recent portability updates and point to targeted regression tests. --- docs/developer/developer.adoc | 68 +++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/docs/developer/developer.adoc b/docs/developer/developer.adoc index 8bb4f86d63e..72d0f5982f5 100644 --- a/docs/developer/developer.adoc +++ b/docs/developer/developer.adoc @@ -168,6 +168,34 @@ Now you can execute the following command to run library self-checks: $ ctest ---- +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 +236,46 @@ $ 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. +* 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, +* `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)`, +* 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/probes/test_memusage_platform.sh` +* `tests/probes/password/test_probes_password_offline_fallback.sh` +* `tests/probes/sysctl/test_sysctl_probe.sh` + . *Install* + -- From 995fe17e8ddb6b1242b1975606a279f6e1cb7b49 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Wed, 11 Mar 2026 13:14:46 -0700 Subject: [PATCH 05/25] Complete CTest label rollout for RPM probe suites Apply the same label-based registration pattern to nested RPM probe test CMake files so selection behavior is consistent with the rest of the probe tree. --- tests/probes/rpm/rpminfo/CMakeLists.txt | 4 ++-- tests/probes/rpm/rpmverify/CMakeLists.txt | 4 ++-- tests/probes/rpm/rpmverifyfile/CMakeLists.txt | 6 +++--- tests/probes/rpm/rpmverifypackage/CMakeLists.txt | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) 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() From 9c2aceafbc3d6c856aaeb7cfa5c272da3959d061 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Wed, 11 Mar 2026 14:28:02 -0700 Subject: [PATCH 06/25] update for macos install and mem tests --- CMakeLists.txt | 2 +- src/common/memusage.c | 6 +++++- tests/API/probes/test_memusage.c | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3efc6efc071..8bc76358c0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,7 +227,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) diff --git a/src/common/memusage.c b/src/common/memusage.c index df30ee6dfd3..8cd80ba237f 100644 --- a/src/common/memusage.c +++ b/src/common/memusage.c @@ -62,7 +62,7 @@ #include "memusage.h" #include "bfind.h" -#if defined(OS_LINUX) || defined(__FreeBSD__) || defined(OS_SOLARIS) +#if defined(OS_LINUX) || defined(__FreeBSD__) || defined(OS_SOLARIS) || defined(OSCAP_TEST_READ_COMMON_SIZET) static int read_common_sizet(void *szp, char *strval) { char *end; @@ -88,6 +88,10 @@ static int read_common_sizet(void *szp, char *strval) return (0); } +#endif + +#if defined(OS_LINUX) || defined(__FreeBSD__) || defined(OS_SOLARIS) + struct stat_parser { char *keyword; int (*storval)(void *, char *); 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() { From 0a46f43b8127ed0e6af98ba7dc5fc0adbaf79643 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Thu, 19 Mar 2026 11:09:15 -0700 Subject: [PATCH 07/25] Add macOS regression coverage for portability fixes --- tests/API/XCCDF/unittests/CMakeLists.txt | 5 ++ .../test_xccdf_result_sysinfo_platform.c | 51 +++++++++++++++++++ .../test_xccdf_result_sysinfo_platform.sh | 14 +++++ tests/probes/password/CMakeLists.txt | 2 +- .../password/test_probes_password_offline.sh | 6 +-- .../test_probes_password_offline_fallback.sh | 4 +- tests/probes/runlevel/CMakeLists.txt | 1 + .../test_probes_runlevel_unsupported.sh | 32 ++++++++++++ 8 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.c create mode 100755 tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh create mode 100755 tests/probes/runlevel/test_probes_runlevel_unsupported.sh diff --git a/tests/API/XCCDF/unittests/CMakeLists.txt b/tests/API/XCCDF/unittests/CMakeLists.txt index 674e2b29b1f..a075ba6033b 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) 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..d0e674127a3 --- /dev/null +++ b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "xccdf_benchmark.h" + +int main(void) +{ + struct xccdf_result *result = xccdf_result_new(); + struct xccdf_target_fact_iterator *facts = NULL; + bool saw_mac = false; + bool saw_asset_mac = false; + + xccdf_result_fill_sysinfo(result); + facts = xccdf_result_get_target_facts(result); + + while (xccdf_target_fact_iterator_has_more(facts)) { + 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 (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; + } + + xccdf_target_fact_iterator_free(facts); + xccdf_result_free(result); + +#if defined(OS_APPLE) + if (!saw_mac) { + fprintf(stderr, "Expected at least one urn:xccdf:fact:ethernet:MAC fact on macOS.\n"); + return 1; + } + if (!saw_asset_mac) { + fprintf(stderr, "Expected at least one urn:xccdf:fact:asset:identifier:mac fact on macOS.\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..20039fccd25 --- /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) ;; + *) exit 255 ;; +esac + +./test_xccdf_result_sysinfo_platform diff --git a/tests/probes/password/CMakeLists.txt b/tests/probes/password/CMakeLists.txt index 04c9b8e21db..47db083377e 100644 --- a/tests/probes/password/CMakeLists.txt +++ b/tests/probes/password/CMakeLists.txt @@ -1,5 +1,5 @@ if(ENABLE_PROBES_UNIX) add_oscap_test("test_probes_password.sh" LABELS unix freebsd macos) - add_oscap_test("test_probes_password_offline.sh" LABELS unix) + 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_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 index 3b327295810..baf02d674a7 100755 --- a/tests/probes/password/test_probes_password_offline_fallback.sh +++ b/tests/probes/password/test_probes_password_offline_fallback.sh @@ -26,9 +26,9 @@ invalid_line_without_separators root:x:0:0:root:/root:/bin/bash EOF - set_chroot_offline_test_mode "$tmpdir" + set_offline_chroot_dir "$tmpdir" $OSCAP oval eval --results "$RF" "$DF" - unset_chroot_offline_test_mode + set_offline_chroot_dir "" rm -rf "$tmpdir" if [ -f "$RF" ]; then diff --git a/tests/probes/runlevel/CMakeLists.txt b/tests/probes/runlevel/CMakeLists.txt index aaaec1e1ccb..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" 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..756cc78309b --- /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 From c58332d946fb67a45adca5fb5410992923939789 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Thu, 19 Mar 2026 11:13:18 -0700 Subject: [PATCH 08/25] Add shadow offline unsupported regression on macOS --- tests/probes/shadow/CMakeLists.txt | 1 + .../test_probes_shadow_offline_unsupported.sh | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100755 tests/probes/shadow/test_probes_shadow_offline_unsupported.sh diff --git a/tests/probes/shadow/CMakeLists.txt b/tests/probes/shadow/CMakeLists.txt index f2769063401..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" 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..92d0e9e3f1a --- /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 From 9253a39c110370cac7ffda8ca87281891bdd6025 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Thu, 19 Mar 2026 12:41:26 -0700 Subject: [PATCH 09/25] Reset stale cached Perl paths during configure --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bc76358c0b..df38f6f6719 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) From 066287b7a6e736030846a20485e3561d3de42d71 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Fri, 20 Mar 2026 10:47:17 -0700 Subject: [PATCH 10/25] changes to enable freebsd builds --- src/OVAL/probes/unix/interface_probe.c | 2 + src/OVAL/probes/unix/sysctl_probe.c | 180 ++++++++++++++----- src/XCCDF_POLICY/xccdf_policy_remediate.c | 2 +- tests/probes/sysctl/test_sysctl_probe_all.sh | 17 +- 4 files changed, 152 insertions(+), 49 deletions(-) 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/sysctl_probe.c b/src/OVAL/probes/unix/sysctl_probe.c index 79fc3543ef3..a0ae6de4f51 100644 --- a/src/OVAL/probes/unix/sysctl_probe.c +++ b/src/OVAL/probes/unix/sysctl_probe.c @@ -36,6 +36,7 @@ #include "sysctl_probe.h" #if defined(OS_FREEBSD) +#include #include #include #include @@ -45,6 +46,7 @@ #define SYSCTL_CMD "/sbin/sysctl -ae" #elif defined(OS_APPLE) +#include #include #include #include @@ -317,6 +319,85 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) #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) +{ + size_t line_len, new_len; + char *new_value; + + line_len = strlen(line); + new_len = *value_len + line_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, line_len + 1); + *value_len += line_len; + return 0; +} + +static int sysctl_collect_item(probe_ctx *ctx, SEXP_t *name_entity, + const char *mib, const char *sysval) +{ + SEXP_t *item, *se_mib; + + 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) { + 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"); + SEXP_free(se_mib); + return PROBE_ENOENT; + } + + probe_item_collect(ctx, item); + } + + SEXP_free(se_mib); + return 0; +} + int sysctl_probe_offline_mode_supported(void) { return PROBE_OFFLINE_NONE; @@ -326,11 +407,11 @@ 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; SEXP_t *name_entity, *probe_in; + int ret = 0; + char *current_mib = NULL; + char *current_value = NULL; + size_t current_value_len = 0; probe_in = probe_ctx_getobject(ctx); name_entity = probe_obj_getent(probe_in, "name", 1); @@ -354,50 +435,59 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) } 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); + 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 : ""); + if (ret != 0) + goto cleanup; + } + + free(current_mib); + free(current_value); + current_mib = strdup(mib); + current_value = NULL; + current_value_len = 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) != 0) { + dE("Failed to append sysctl value"); + ret = PROBE_ENOENT; + goto cleanup; + } + + continue; + } + + if (current_mib == NULL) + continue; + + if (sysctl_append_value(¤t_value, ¤t_value_len, output) != 0) { + dE("Failed to append sysctl continuation value"); + ret = PROBE_ENOENT; + goto cleanup; + } } + if (current_mib != NULL) + ret = sysctl_collect_item(ctx, name_entity, current_mib, + current_value != NULL ? current_value : ""); + +cleanup: + free(current_mib); + free(current_value); pclose(fp); - return (0); + return ret; } #else diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 5e4a0b11168..9026c1ac2b3 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -35,6 +35,7 @@ #include #else #include +extern char **environ; #endif #include @@ -2068,4 +2069,3 @@ int xccdf_policy_generate_fix(struct xccdf_policy *policy, struct xccdf_result * return ret; } - diff --git a/tests/probes/sysctl/test_sysctl_probe_all.sh b/tests/probes/sysctl/test_sysctl_probe_all.sh index 6dcbefe8a23..1934d74c3b7 100755 --- a/tests/probes/sysctl/test_sysctl_probe_all.sh +++ b/tests/probes/sysctl/test_sysctl_probe_all.sh @@ -34,10 +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 -aN 2> /dev/null > "$sysctlNames" + sysctl -ae 2> /dev/null | \ + grep -E '^[[:alpha:]][[:alnum:]_.%-]*=' | \ + cut -d "=" -f 1 | \ + sort -u > "$sysctlNames" ;; Linux) # sysctl has duplicities in output @@ -50,7 +56,12 @@ function perform_test { ;; 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" | \ + xsed -E 's;.*([^<]+).*;\1;g' | \ + sort > "$ourNames" echo "Diff (sysctlNames / ourNames): ------" diff "$sysctlNames" "$ourNames" From 313542aed797cfffaa28a9cabd0f0742f0b2ec4f Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Fri, 20 Mar 2026 11:16:38 -0700 Subject: [PATCH 11/25] updated documentation with macos/freebsd build info --- docs/contribute/testing.adoc | 17 +++++++++++++++++ docs/developer/developer.adoc | 27 +++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) 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 72d0f5982f5..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,15 @@ 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: @@ -255,6 +270,8 @@ Notes: 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 @@ -262,19 +279,26 @@ Notes: The codebase contains recent portability work for macOS/FreeBSD, including: * `sysctl` probe support for macOS (`/usr/sbin/sysctl -ae`) and FreeBSD/macOS - branching, + 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* + @@ -437,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. - From 901e2501eda20f32a12a664aa82a8348ca5ebd76 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Fri, 20 Mar 2026 11:47:17 -0700 Subject: [PATCH 12/25] replace use of strlen() --- src/OVAL/probes/unix/password_probe.c | 43 ++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/OVAL/probes/unix/password_probe.c b/src/OVAL/probes/unix/password_probe.c index 8f4d6084b2a..c3eec9e7113 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) @@ -72,14 +73,43 @@ * This parses a standard /etc/passwd-format file one entry at a time. */ #ifndef HAVE_FGETPWENT +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], *p; + 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] == '#' || line[0] == '\n') continue; f = 0; @@ -94,14 +124,13 @@ static struct passwd *oscap_fgetpwent(FILE *fp) } if (f < 7) continue; - /* strip trailing newline from shell field */ - size_t n = strlen(fields[6]); - if (n > 0 && fields[6][n - 1] == '\n') - fields[6][n - 1] = '\0'; + 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)atoi(fields[2]); - pw.pw_gid = (gid_t)atoi(fields[3]); + 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]; From 5d719ba819e81ac6d0eebdf520a3a92d43200aa3 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Fri, 20 Mar 2026 12:07:17 -0700 Subject: [PATCH 13/25] fix password test issue for freebsd/macos --- .../password/test_probes_password.xml.sh | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/tests/probes/password/test_probes_password.xml.sh b/tests/probes/password/test_probes_password.xml.sh index 2a630d4b933..105310eb13a 100644 --- a/tests/probes/password/test_probes_password.xml.sh +++ b/tests/probes/password/test_probes_password.xml.sh @@ -11,10 +11,11 @@ if which systemctl &>/dev/null && \ 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 + # 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" ;; @@ -33,10 +34,30 @@ function getField { echo $LINE | awk -F':' '{print $2}' ;; 'user_id' ) - echo $LINE | awk -F':' '{print $3}' + case `uname` in + FreeBSD) + id -u "`echo $LINE | awk -F':' '{print $1}'`" + ;; + Darwin) + id -u "`echo $LINE | awk -F':' '{print $1}'`" + ;; + *) + echo $LINE | awk -F':' '{print $3}' + ;; + esac ;; 'group_id' ) - echo $LINE | awk -F':' '{print $4}' + case `uname` in + FreeBSD) + id -g "`echo $LINE | awk -F':' '{print $1}'`" + ;; + Darwin) + id -g "`echo $LINE | awk -F':' '{print $1}'`" + ;; + *) + echo $LINE | awk -F':' '{print $4}' + ;; + esac ;; 'gcos' ) echo $LINE | awk -F':' '{gsub(/&/,"&",$5); print $5}' From 21ce4d12afc367d345fb4bbbbd3c1c52a167d324 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Fri, 20 Mar 2026 12:50:33 -0700 Subject: [PATCH 14/25] remove bogus line --- tests/API/probes/test_memusage_platform.c | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/API/probes/test_memusage_platform.c b/tests/API/probes/test_memusage_platform.c index 0b38c43e68f..67a27405d99 100644 --- a/tests/API/probes/test_memusage_platform.c +++ b/tests/API/probes/test_memusage_platform.c @@ -8,7 +8,6 @@ #include #include "memusage.h" -#include "memusage.c" int main(void) { From 409e4b4e6c9ada6a790076e8626ce9b90802cfae Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Fri, 20 Mar 2026 13:08:16 -0700 Subject: [PATCH 15/25] exapnded freebsd testing --- tests/API/XCCDF/unittests/CMakeLists.txt | 2 +- .../unittests/test_xccdf_result_sysinfo_platform.c | 14 +++++++++++--- .../test_xccdf_result_sysinfo_platform.sh | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/API/XCCDF/unittests/CMakeLists.txt b/tests/API/XCCDF/unittests/CMakeLists.txt index a075ba6033b..051614c8b8e 100644 --- a/tests/API/XCCDF/unittests/CMakeLists.txt +++ b/tests/API/XCCDF/unittests/CMakeLists.txt @@ -113,7 +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) +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 index d0e674127a3..f715bf21431 100644 --- a/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.c +++ b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.c @@ -10,12 +10,18 @@ #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); @@ -27,22 +33,24 @@ int main(void) 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(OS_APPLE) +#if defined(EXPECT_MAC_FACTS) if (!saw_mac) { - fprintf(stderr, "Expected at least one urn:xccdf:fact:ethernet:MAC fact on macOS.\n"); + 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 macOS.\n"); + fprintf(stderr, "Expected at least one urn:xccdf:fact:asset:identifier:mac fact on this platform.\n"); return 1; } #endif diff --git a/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh index 20039fccd25..fb1e942f4bb 100755 --- a/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh +++ b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh @@ -7,7 +7,7 @@ if [ -n "${CUSTOM_OSCAP+x}" ] ; then fi case "$(uname)" in - Darwin) ;; + Darwin|FreeBSD) ;; *) exit 255 ;; esac From 953b8136ad3cdb616ca943eca8cb5599da792246 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Fri, 20 Mar 2026 13:33:35 -0700 Subject: [PATCH 16/25] fix freebsd builds --- tests/API/probes/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/API/probes/CMakeLists.txt b/tests/API/probes/CMakeLists.txt index ce6197d98cf..9b8cd4b6bc6 100644 --- a/tests/API/probes/CMakeLists.txt +++ b/tests/API/probes/CMakeLists.txt @@ -51,6 +51,7 @@ 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 From b1f213e2bb5fb75b879a68d7deff1321ba8324ab Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Fri, 20 Mar 2026 13:44:13 -0700 Subject: [PATCH 17/25] fix freebsd sysctl handling --- src/OVAL/probes/unix/sysctl_probe.c | 148 +++++++++++++++++++++++++--- 1 file changed, 137 insertions(+), 11 deletions(-) diff --git a/src/OVAL/probes/unix/sysctl_probe.c b/src/OVAL/probes/unix/sysctl_probe.c index a0ae6de4f51..ba1a5025aa9 100644 --- a/src/OVAL/probes/unix/sysctl_probe.c +++ b/src/OVAL/probes/unix/sysctl_probe.c @@ -369,10 +369,105 @@ static int sysctl_append_value(char **value, size_t *value_len, const char *line 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) + 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) { @@ -381,21 +476,46 @@ static int sysctl_collect_item(probe_ctx *ctx, SEXP_t *name_entity, } if (probe_entobj_cmp(name_entity, se_mib) == OVAL_RESULT_TRUE) { - item = probe_item_create(OVAL_UNIX_SYSCTL, NULL, - "name", OVAL_DATATYPE_SEXP, se_mib, - "value", OVAL_DATATYPE_STRING, sysval, - NULL); + 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"); - SEXP_free(se_mib); - return PROBE_ENOENT; + 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 0; + return ret; } int sysctl_probe_offline_mode_supported(void) @@ -408,6 +528,8 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) FILE *fp; char output[LINE_MAX]; 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; @@ -415,6 +537,8 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) 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"); @@ -444,7 +568,8 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) 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 : ""); + current_value != NULL ? current_value : "", + over_cmp); if (ret != 0) goto cleanup; } @@ -477,11 +602,12 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) ret = PROBE_ENOENT; goto cleanup; } - } + } if (current_mib != NULL) ret = sysctl_collect_item(ctx, name_entity, current_mib, - current_value != NULL ? current_value : ""); + current_value != NULL ? current_value : "", + over_cmp); cleanup: free(current_mib); From d24c9d4e174557561b006c843379a8b695649435 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Fri, 20 Mar 2026 14:02:36 -0700 Subject: [PATCH 18/25] fix freebsd sysctl segfault --- src/OVAL/probes/unix/sysctl_probe.c | 54 +++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/OVAL/probes/unix/sysctl_probe.c b/src/OVAL/probes/unix/sysctl_probe.c index ba1a5025aa9..7c49164144a 100644 --- a/src/OVAL/probes/unix/sysctl_probe.c +++ b/src/OVAL/probes/unix/sysctl_probe.c @@ -41,21 +41,25 @@ #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) @@ -349,13 +353,25 @@ static int sysctl_parse_line(char *line, char **mib, char **sysval) return 1; } -static int sysctl_append_value(char **value, size_t *value_len, const char *line) +static int sysctl_append_value(char **value, size_t *value_len, const char *line, int *truncated) { - size_t line_len, new_len; + size_t line_len, append_len, new_len; char *new_value; + if (*truncated) + return 0; + line_len = strlen(line); - new_len = *value_len + line_len + (*value_len > 0 ? 1 : 0) + 1; + 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; @@ -364,8 +380,13 @@ static int sysctl_append_value(char **value, size_t *value_len, const char *line if (*value_len > 0) new_value[(*value_len)++] = '\n'; - memcpy(new_value + *value_len, line, line_len + 1); - *value_len += line_len; + 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; } @@ -526,7 +547,9 @@ int sysctl_probe_offline_mode_supported(void) int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) { FILE *fp; - char output[LINE_MAX]; + 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; @@ -534,6 +557,7 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) 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); @@ -558,7 +582,7 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) return (PROBE_EFATAL); } - while (fgets(output, sizeof(output), fp)) { + while ((output_len = getline(&output, &output_cap, fp)) != -1) { char *mib, *sysval, *newline; newline = strchr(output, '\n'); @@ -579,30 +603,39 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) 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) != 0) { + 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) != 0) { + 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, @@ -610,6 +643,7 @@ int sysctl_probe_main(probe_ctx *ctx, void *probe_arg) over_cmp); cleanup: + free(output); free(current_mib); free(current_value); pclose(fp); From 601cb0f344ef4243f5f6ee5f80761c6f350a2f71 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Fri, 20 Mar 2026 14:37:21 -0700 Subject: [PATCH 19/25] add freebsd specific memory usage tests --- src/common/memusage.c | 39 ++++++----- tests/API/probes/CMakeLists.txt | 11 ++++ .../API/probes/test_memusage_freebsd_stress.c | 66 +++++++++++++++++++ .../probes/test_memusage_freebsd_stress.sh | 9 +++ 4 files changed, 107 insertions(+), 18 deletions(-) create mode 100644 tests/API/probes/test_memusage_freebsd_stress.c create mode 100755 tests/API/probes/test_memusage_freebsd_stress.sh diff --git a/src/common/memusage.c b/src/common/memusage.c index 8cd80ba237f..a484e5ff3e8 100644 --- a/src/common/memusage.c +++ b/src/common/memusage.c @@ -33,10 +33,7 @@ #include #if defined(OS_FREEBSD) -#include -#include #include -#include #include #include #include @@ -262,33 +259,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; diff --git a/tests/API/probes/CMakeLists.txt b/tests/API/probes/CMakeLists.txt index 9b8cd4b6bc6..5f31ebbb84f 100644 --- a/tests/API/probes/CMakeLists.txt +++ b/tests/API/probes/CMakeLists.txt @@ -58,3 +58,14 @@ 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_freebsd_stress.c b/tests/API/probes/test_memusage_freebsd_stress.c new file mode 100644 index 00000000000..38be19214c7 --- /dev/null +++ b/tests/API/probes/test_memusage_freebsd_stress.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "memusage.h" + +#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; +} + +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..4ea1c74b3ba --- /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 From ac907c32b7d27365b106e6db798edc7a4b7ce5d8 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Fri, 20 Mar 2026 14:45:15 -0700 Subject: [PATCH 20/25] map sed syntax for freebsd instead of assuming we have gsed installed --- tests/probes/sysctl/test_sysctl_probe_all.sh | 2 +- tests/test_common.sh.in | 30 +++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/probes/sysctl/test_sysctl_probe_all.sh b/tests/probes/sysctl/test_sysctl_probe_all.sh index 1934d74c3b7..348f3d6b9b7 100755 --- a/tests/probes/sysctl/test_sysctl_probe_all.sh +++ b/tests/probes/sysctl/test_sysctl_probe_all.sh @@ -60,7 +60,7 @@ function perform_test { # on the same line as the element. Match the explicit name # tags so value payloads do not pollute the extracted key list. grep '' "$result" | \ - xsed -E 's;.*([^<]+).*;\1;g' | \ + sed -E 's;.*([^<]+).*;\1;g' | \ sort > "$ourNames" echo "Diff (sysctlNames / ourNames): ------" 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 "$@" From 67a1fa69c008ed11a9bce7adaf92757c2d63a225 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Mon, 23 Mar 2026 06:49:41 -0700 Subject: [PATCH 21/25] fix macos password test and freebsd memory test --- CMakeLists.txt | 1 - config.h.in | 1 - src/OVAL/probes/unix/password_probe.c | 2 +- tests/API/probes/test_memusage_freebsd_stress.c | 4 ++++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index df38f6f6719..2d178990e10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -212,7 +212,6 @@ check_function_exists(memalign HAVE_MEMALIGN) check_function_exists(fts_open HAVE_FTS_OPEN) check_function_exists(strsep HAVE_STRSEP) check_function_exists(strptime HAVE_STRPTIME) -check_function_exists(fgetpwent HAVE_FGETPWENT) check_include_file(syslog.h HAVE_SYSLOG_H) check_include_file(stdio_ext.h HAVE_STDIO_EXT_H) diff --git a/config.h.in b/config.h.in index 4cf7f638d43..305bd231cfb 100644 --- a/config.h.in +++ b/config.h.in @@ -85,7 +85,6 @@ #cmakedefine HAVE_STRSEP #cmakedefine HAVE_FLOCK #cmakedefine HAVE_STRPTIME -#cmakedefine HAVE_FGETPWENT #cmakedefine OPENSCAP_PROBE_INDEPENDENT_ENVIRONMENTVARIABLE #cmakedefine OPENSCAP_PROBE_INDEPENDENT_ENVIRONMENTVARIABLE58 diff --git a/src/OVAL/probes/unix/password_probe.c b/src/OVAL/probes/unix/password_probe.c index c3eec9e7113..d7783eb91e1 100644 --- a/src/OVAL/probes/unix/password_probe.c +++ b/src/OVAL/probes/unix/password_probe.c @@ -72,7 +72,7 @@ * fgetpwent() is a GNU/glibc extension; provide a portable fallback. * This parses a standard /etc/passwd-format file one entry at a time. */ -#ifndef HAVE_FGETPWENT +#if defined(OS_APPLE) || defined(OS_FREEBSD) static int oscap_parse_passwd_id(const char *field, uintmax_t max_value, uintmax_t *parsed_value) { diff --git a/tests/API/probes/test_memusage_freebsd_stress.c b/tests/API/probes/test_memusage_freebsd_stress.c index 38be19214c7..b7a9e0e8a0d 100644 --- a/tests/API/probes/test_memusage_freebsd_stress.c +++ b/tests/API/probes/test_memusage_freebsd_stress.c @@ -5,11 +5,14 @@ #endif #include +#if defined(OS_FREEBSD) #include +#endif #include #include "memusage.h" +#if defined(OS_FREEBSD) #define STRESS_THREADS 4 #define STRESS_ITERATIONS 2000 @@ -34,6 +37,7 @@ static void *worker(void *arg) result->err = 0; return NULL; } +#endif int main(void) { From 033a8bdc33175f200c77cdf285876db9b8680d4f Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Mon, 23 Mar 2026 07:13:55 -0700 Subject: [PATCH 22/25] keep include directives together --- src/XCCDF_POLICY/xccdf_policy_remediate.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/XCCDF_POLICY/xccdf_policy_remediate.c b/src/XCCDF_POLICY/xccdf_policy_remediate.c index 9026c1ac2b3..069df288c98 100644 --- a/src/XCCDF_POLICY/xccdf_policy_remediate.c +++ b/src/XCCDF_POLICY/xccdf_policy_remediate.c @@ -35,7 +35,6 @@ #include #else #include -extern char **environ; #endif #include @@ -413,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; } From c48a4e4bf0e15499710e50ae2bd8ae95d3841b23 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Mon, 23 Mar 2026 07:52:29 -0700 Subject: [PATCH 23/25] address sonarqube issues --- src/common/memusage.c | 24 +++---- .../test_xccdf_result_sysinfo_platform.c | 2 +- .../test_xccdf_result_sysinfo_platform.sh | 2 +- .../probes/test_memusage_freebsd_stress.sh | 2 +- tests/API/probes/test_memusage_platform.sh | 2 +- .../password/test_probes_password.xml.sh | 64 +++++++++++-------- .../test_probes_password_offline_fallback.sh | 12 ++-- .../test_probes_runlevel_unsupported.sh | 2 +- .../test_probes_shadow_offline_unsupported.sh | 2 +- 9 files changed, 58 insertions(+), 54 deletions(-) diff --git a/src/common/memusage.c b/src/common/memusage.c index a484e5ff3e8..c2faec5da00 100644 --- a/src/common/memusage.c +++ b/src/common/memusage.c @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -60,29 +61,24 @@ #include "bfind.h" #if defined(OS_LINUX) || defined(__FreeBSD__) || defined(OS_SOLARIS) || defined(OSCAP_TEST_READ_COMMON_SIZET) -static int read_common_sizet(void *szp, char *strval) +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 @@ -91,7 +87,7 @@ static int read_common_sizet(void *szp, char *strval) struct stat_parser { char *keyword; - int (*storval)(void *, char *); + int (*storval)(void *, const char *); ptrdiff_t offset; }; diff --git a/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.c b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.c index f715bf21431..311de638cbb 100644 --- a/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.c +++ b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.c @@ -27,7 +27,7 @@ int main(void) facts = xccdf_result_get_target_facts(result); while (xccdf_target_fact_iterator_has_more(facts)) { - struct xccdf_target_fact *fact = xccdf_target_fact_iterator_next(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) diff --git a/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh index fb1e942f4bb..89c3cc46801 100755 --- a/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh +++ b/tests/API/XCCDF/unittests/test_xccdf_result_sysinfo_platform.sh @@ -2,7 +2,7 @@ . $builddir/tests/test_common.sh -if [ -n "${CUSTOM_OSCAP+x}" ] ; then +if [[ -n "${CUSTOM_OSCAP+x}" ]] ; then exit 255 fi diff --git a/tests/API/probes/test_memusage_freebsd_stress.sh b/tests/API/probes/test_memusage_freebsd_stress.sh index 4ea1c74b3ba..d55c3fdb56c 100755 --- a/tests/API/probes/test_memusage_freebsd_stress.sh +++ b/tests/API/probes/test_memusage_freebsd_stress.sh @@ -2,7 +2,7 @@ . $builddir/tests/test_common.sh -if [ "$(uname)" != "FreeBSD" ] ; then +if [[ "$(uname)" != "FreeBSD" ]] ; then exit 255 fi diff --git a/tests/API/probes/test_memusage_platform.sh b/tests/API/probes/test_memusage_platform.sh index 74cde71f3a9..845d423035c 100755 --- a/tests/API/probes/test_memusage_platform.sh +++ b/tests/API/probes/test_memusage_platform.sh @@ -2,7 +2,7 @@ . $builddir/tests/test_common.sh -if [ -n "${CUSTOM_OSCAP+x}" ] ; then +if [[ -n "${CUSTOM_OSCAP+x}" ]] ; then exit 255 fi diff --git a/tests/probes/password/test_probes_password.xml.sh b/tests/probes/password/test_probes_password.xml.sh index 105310eb13a..de37ee959f5 100644 --- a/tests/probes/password/test_probes_password.xml.sh +++ b/tests/probes/password/test_probes_password.xml.sh @@ -1,16 +1,17 @@ #!/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 + case "$(uname)" in # BSD passwd files may contain comments that are ignored by getpwent(3). Darwin|FreeBSD) grep -Ev '^(#|$)' /etc/passwd > "$passwd_file" @@ -22,51 +23,58 @@ else 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' ) - case `uname` in + case "$(uname)" in FreeBSD) - id -u "`echo $LINE | awk -F':' '{print $1}'`" + id -u "$username" ;; Darwin) - id -u "`echo $LINE | awk -F':' '{print $1}'`" + id -u "$username" ;; *) - echo $LINE | awk -F':' '{print $3}' + echo "$line" | awk -F':' '{print $3}' ;; esac ;; 'group_id' ) - case `uname` in + case "$(uname)" in FreeBSD) - id -g "`echo $LINE | awk -F':' '{print $1}'`" + id -g "$username" ;; Darwin) - id -g "`echo $LINE | awk -F':' '{print $1}'`" + id -g "$username" ;; *) - echo $LINE | awk -F':' '{print $4}' + 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}' ;; esac } @@ -129,7 +137,7 @@ I=1 while [ $I -le $LINES_COUNT ]; do cat < - `getField "username" ${I}` + $(getField "username" "${I}") EOF I=$[$I+1] @@ -145,13 +153,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_fallback.sh b/tests/probes/password/test_probes_password_offline_fallback.sh index baf02d674a7..11b7c31ee73 100755 --- a/tests/probes/password/test_probes_password_offline_fallback.sh +++ b/tests/probes/password/test_probes_password_offline_fallback.sh @@ -13,9 +13,9 @@ function test_probes_password_offline_fallback { *) return 255 ;; esac - local DF="${srcdir}/test_probes_password_offline.xml" - local RF="results.xml" - [ -f "$RF" ] && rm -f "$RF" + 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" @@ -27,12 +27,12 @@ root:x:0:0:root:/root:/bin/bash EOF set_offline_chroot_dir "$tmpdir" - $OSCAP oval eval --results "$RF" "$DF" + $OSCAP oval eval --results "$results_file" "$definition_file" set_offline_chroot_dir "" rm -rf "$tmpdir" - if [ -f "$RF" ]; then - result="$RF" + 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 diff --git a/tests/probes/runlevel/test_probes_runlevel_unsupported.sh b/tests/probes/runlevel/test_probes_runlevel_unsupported.sh index 756cc78309b..7c2e1aa3c9b 100755 --- a/tests/probes/runlevel/test_probes_runlevel_unsupported.sh +++ b/tests/probes/runlevel/test_probes_runlevel_unsupported.sh @@ -14,7 +14,7 @@ function test_probes_runlevel_unsupported { local definition="${top_srcdir}/tests/oval_details/runlevel.oval.xml" local results="results_unsupported.xml" - [ -f "$results" ] && rm -f "$results" + [[ -f "$results" ]] && rm -f "$results" $OSCAP oval eval --results "$results" "$definition" diff --git a/tests/probes/shadow/test_probes_shadow_offline_unsupported.sh b/tests/probes/shadow/test_probes_shadow_offline_unsupported.sh index 92d0e9e3f1a..37d29724189 100755 --- a/tests/probes/shadow/test_probes_shadow_offline_unsupported.sh +++ b/tests/probes/shadow/test_probes_shadow_offline_unsupported.sh @@ -14,7 +14,7 @@ function test_probes_shadow_offline_unsupported { local definition="${srcdir}/test_probes_shadow_offline.xml" local results="results_unsupported.xml" - [ -f "$results" ] && rm -f "$results" + [[ -f "$results" ]] && rm -f "$results" tmpdir=$(make_temp_dir /tmp "test_offline_mode_shadow_unsupported") mkdir -p "${tmpdir}/etc" From 98f9fa5091bda14c705fce9e41def992a4be1ab6 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Mon, 23 Mar 2026 08:08:12 -0700 Subject: [PATCH 24/25] address last sonarqube issue --- tests/probes/password/test_probes_password.xml.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/probes/password/test_probes_password.xml.sh b/tests/probes/password/test_probes_password.xml.sh index de37ee959f5..3af3d0be6f9 100644 --- a/tests/probes/password/test_probes_password.xml.sh +++ b/tests/probes/password/test_probes_password.xml.sh @@ -76,6 +76,10 @@ function getField { 'login_shell' ) echo "$line" | awk -F':' '{print $7}' ;; + * ) + echo "Unexpected field: $field_name" >&2 + return 1 + ;; esac } From 4a91508668cc6597585ea27da0671cf42a382809 Mon Sep 17 00:00:00 2001 From: Ed Silva Date: Wed, 25 Mar 2026 06:44:57 -0700 Subject: [PATCH 25/25] Address review feedback on portability fixes --- src/OVAL/probes/unix/password_probe.c | 2 +- src/common/memusage.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OVAL/probes/unix/password_probe.c b/src/OVAL/probes/unix/password_probe.c index d7783eb91e1..1f0c8374d2b 100644 --- a/src/OVAL/probes/unix/password_probe.c +++ b/src/OVAL/probes/unix/password_probe.c @@ -110,7 +110,7 @@ static struct passwd *oscap_fgetpwent(FILE *fp) continue; } - if (line[0] == '#' || line[0] == '\n') + if (line[0] == '#') continue; f = 0; p = line; diff --git a/src/common/memusage.c b/src/common/memusage.c index c2faec5da00..8d75ee09c1c 100644 --- a/src/common/memusage.c +++ b/src/common/memusage.c @@ -370,8 +370,8 @@ int oscap_proc_memusage(struct proc_memusage *mu) errno = EOPNOTSUPP; return -1; } - mu->mu_rss = info.resident_size / 1024; - mu->mu_data = info.virtual_size / 1024; + 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;