Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Integration
on:
pull_request:
branches: [develop]
paths: ['**.c', '**.h', 'Dockerfile', '.dockerignore', 'tests/snmpv3/**', '.github/workflows/integration.yml']
paths: ['**.c', '**.h', 'Dockerfile', '.dockerignore', 'tests/snmpv3/**', 'tests/integration/test_script_server_reap.sh', '.github/workflows/integration.yml']
workflow_dispatch:

permissions:
Expand All @@ -25,6 +25,9 @@ jobs:
- name: Poll an SNMPv3 device end to end
run: tests/snmpv3/scripts/run-integration.sh

- name: Reap a real PHP script-server process
run: tests/integration/test_script_server_reap.sh

- name: Show container logs on failure
if: failure()
run: |
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The Cacti Group | spine
-issue#552: Terminate die() output so consecutive fatal messages no longer run together
-issue#561: Reserve room for the terminator in php_readpipe() so a full script server result cannot write past result_string
-issue#562: Escalate PHP script server shutdown to SIGKILL after a bounded grace period so a stuck child is not orphaned
-issue#610: Give nft_pclose exclusive ownership of subprocess registry entries
-issue: Correct signed and unsigned printf format specifiers in poller.c, free session.localname on the unknown-version return in snmp.c, and quote shell variables in the build scripts
-issue: Escape the SNMP result and RRD name before the poller_output INSERT, bound the buffer_output_errors write to the space left in error_string, and validate the --hostlist argument before it reaches SQL
-issue: Restore the twelve headers and spine.conf.dist missing from the dist tarball so a release tarball can be compiled from
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libmariadb3 \
libsnmp40 \
libssl3 \
php-cli \
procps \
zlib1g \
&& rm -rf /var/lib/apt/lists/*

Expand Down
27 changes: 26 additions & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ check_PROGRAMS += \
tests/unit/test_build_fixes \
tests/unit/test_safety_fixes \
tests/unit/test_linked

# A small executable which speaks the subset of the PHP Script Server protocol
# needed by test_php_runtime. Keeping it as a real child covers the shipped
# pipe, spawn, command, response, shutdown and reap paths.
noinst_PROGRAMS = tests/unit/php_test_server
tests_unit_php_test_server_SOURCES = tests/unit/php_test_server.c

if HAVE_LD_WRAP
check_PROGRAMS += tests/unit/test_php_runtime
endif
endif

# test_util_strings pulls in common.h and the sources under test, so it needs
Expand All @@ -86,4 +96,19 @@ tests_unit_test_linked_SOURCES = tests/unit/test_linked.c tests/fuzz/stubs.c \
sql.c util.c snmp.c locks.c poller.c nft_popen.c php.c ping.c keywords.c error.c
tests_unit_test_linked_LDADD = $(CMOCKA_LIBS) $(LIBS)

TESTS = $(check_PROGRAMS)
tests_unit_test_php_runtime_SOURCES = tests/unit/test_php_runtime.c tests/fuzz/stubs.c \
sql.c util.c snmp.c locks.c poller.c nft_popen.c php.c ping.c keywords.c error.c
tests_unit_test_php_runtime_LDADD = $(CMOCKA_LIBS) $(LIBS)
tests_unit_test_php_runtime_LDFLAGS = \
-Wl,--wrap=posix_spawn -Wl,--wrap=dup -Wl,--wrap=fcntl \
-Wl,--wrap=pthread_sigmask -Wl,--wrap=write \
-Wl,--wrap=posix_spawnattr_setsigdefault -Wl,--wrap=posix_spawnattr_destroy
tests_unit_test_php_runtime_CPPFLAGS = $(AM_CPPFLAGS) \
-DPHP_TEST_SERVER_PATH='"$(abs_builddir)/tests/unit/php_test_server"' \
-DSPINE_PHP_RUNTIME_TESTING
tests_unit_test_php_runtime_DEPENDENCIES = tests/unit/php_test_server$(EXEEXT)

TEST_EXTENSIONS = .sh
SH_LOG_COMPILER = $(SHELL)
TESTS = $(check_PROGRAMS) tests/regression/test_child_process_safety.sh
EXTRA_DIST += tests/regression/test_child_process_safety.sh
20 changes: 20 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,26 @@ AC_DEFINE_UNQUOTED(SNMP_LOCALNAME, $havelocalname, If snmp localname session str

AC_CHECK_LIB(netsnmp, snmp_timeout)

# ****************** Linker --wrap support (tests only) *********************
dnl The PHP runtime suite interposes on process and descriptor operations so
dnl destructive failure paths are deterministic. GNU ld and lld provide the
dnl required --wrap option; probe it so other supported linkers skip cleanly.
AC_MSG_CHECKING([whether the linker supports --wrap])
spine_save_LDFLAGS="$LDFLAGS"
LDFLAGS="$LDFLAGS -Wl,--wrap=spine_ld_wrap_probe"
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
int spine_ld_wrap_probe(void);
int __wrap_spine_ld_wrap_probe(void);
int __wrap_spine_ld_wrap_probe(void) { return 0; }
]], [[
return spine_ld_wrap_probe();
]])],
[spine_ld_wrap=yes],
[spine_ld_wrap=no])
LDFLAGS="$spine_save_LDFLAGS"
AC_MSG_RESULT($spine_ld_wrap)
AM_CONDITIONAL([HAVE_LD_WRAP], [test "x$spine_ld_wrap" = "xyes"])

# ****************** SNMPv3 USM Error Constants Check ***********************
AC_MSG_CHECKING([for SNMPv3 USM error constants])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
Expand Down
33 changes: 29 additions & 4 deletions error.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,6 @@ static void spine_signal_handler(int spine_signal) {
case SIGQUIT:
message = "FATAL: Spine Encountered a Keyboard Quit Command\n";
break;
case SIGPIPE:
message = "FATAL: Spine Encountered a Broken Pipe\n";
break;
default:
break;
}
Expand All @@ -149,7 +146,6 @@ static void spine_signal_handler(int spine_signal) {

static int spine_fatal_signals[] = {
SIGINT,
SIGPIPE,
SIGSEGV,
SIGBUS,
SIGFPE,
Expand All @@ -159,6 +155,13 @@ static int spine_fatal_signals[] = {
0
};

/* A caught disposition is reset to SIG_DFL by exec(), unlike SIG_IGN. Keep
* broken pipes non-fatal in Spine while preserving normal SIGPIPE semantics
* for operator scripts launched through either posix_spawn() or libc popen(). */
static void spine_sigpipe_handler(int spine_signal) {
(void) spine_signal;
}

/*! \fn void install_spine_signal_handler(void)
* \brief installs the spine signal handler to stop certain calls from
* abending Spine.
Expand All @@ -170,6 +173,17 @@ void install_spine_signal_handler(void) {
struct sigaction sa;
void (*ohandler)(int);

/* Broken pipes are ordinary runtime failures for database sockets, script
* pipes and redirected logs. A caught handler makes write() return EPIPE,
* and exec'd children automatically regain SIG_DFL. */
memset(&sa, 0, sizeof(sa));
sa.sa_handler = spine_sigpipe_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGPIPE, &sa, NULL) != 0) {
SPINE_LOG(("ERROR: Unable to install SIGPIPE handler: %s", strerror(errno)));
}

for (i=0; spine_fatal_signals[i]; ++i) {
sigaction(spine_fatal_signals[i], NULL, &sa);
if (sa.sa_handler == SIG_DFL) {
Expand Down Expand Up @@ -200,6 +214,17 @@ void uninstall_spine_signal_handler(void) {
struct sigaction sa;
void (*ohandler)(int);

if (sigaction(SIGPIPE, NULL, &sa) != 0) {
SPINE_LOG(("WARNING: Unable to inspect SIGPIPE handler during shutdown: %s", strerror(errno)));
} else if (sa.sa_handler == spine_sigpipe_handler) {
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGPIPE, &sa, NULL) != 0) {
SPINE_LOG(("WARNING: Unable to restore the default SIGPIPE handler: %s", strerror(errno)));
}
}

for (i=0; spine_fatal_signals[i]; ++i) {
sigaction(spine_fatal_signals[i], NULL, &sa);
if (sa.sa_handler == spine_signal_handler) {
Expand Down
Loading
Loading