mirror of
https://github.com/Cacti/spine.git
synced 2026-10-23 03:27:10 +09:00
Open and own the required ICMP sockets before permanently dropping setuid root, then parse input without elevated identity. Close-on-exec socket ownership, shared IPv4/IPv6 dispatch and fault-injected identity/capability calls are covered. The owned fixture tests check deny/allow behavior without contacting external devices. Step 2/14 of the complete C17/native-correctness series. Base: the preceding series branch; merge/review in order so this PR shows only its own step. Earlier validation, before this review follow-up, in isolated copied snapshots on Linux arm64: - Clean GCC build and make check: 13 passed, 1 existing prerequisite skips, 0 failures. - ASan/UBSan: 12 passed, 2 existing privilege/allocator-interposition prerequisite skips, 0 failures; no sanitizer findings. - Production source/profile integrity verification against disposable MariaDB and loopback SNMP fixtures passed. - Release archive extracted into a separate source tree and built/tested out of tree; fuzz checks passed. Earlier local profiles retain source hashes for the copied validation tree; GitHub checks validate the published commit. No production database/device, host installation or live privilege change is part of validation. Existing prerequisite skips were retained; no test or finding was suppressed to obtain a pass. Series navigation: [Series index](https://github.com/Cacti/spine/pull/638); [previous step](https://github.com/Cacti/spine/pull/638). [Next step](https://github.com/Cacti/spine/pull/640). Review follow-up (2026-10-09): Restacked on the corrected preceding branches. The SNMP status fix is introduced in #638, the reindex/completion fixes in #640, and focused regression coverage in #650. Restacked tip validation: isolated Linux arm64 build and make check passed (18 passed, 1 setuid prerequisite skip, no failures). GitHub checks validate the updated published commits; earlier profile/archive/sanitizer evidence above belongs to the previous revisions. --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
260 lines
8.5 KiB
C
260 lines
8.5 KiB
C
/*
|
|
ex: set tabstop=4 shiftwidth=4 autoindent:
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| This program is free software; you can redistribute it and/or |
|
|
| modify it under the terms of the GNU Lesser General Public |
|
|
| License as published by the Free Software Foundation; either |
|
|
| version 2.1 of the License, or (at your option) any later version. |
|
|
| |
|
|
| This program is distributed in the hope that it will be useful, |
|
|
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
| GNU Lesser General Public License for more details. |
|
|
| |
|
|
| You should have received a copy of the GNU Lesser General Public |
|
|
| License along with this library; if not, write to the Free Software |
|
|
| Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
|
| 02110-1301, USA |
|
|
| |
|
|
+-------------------------------------------------------------------------+
|
|
| spine: a backend data gatherer for cacti |
|
|
+-------------------------------------------------------------------------+
|
|
| This poller would not have been possible without: |
|
|
| - Larry Adams (current development and enhancements) |
|
|
| - Rivo Nurges (rrd support, mysql poller cache, misc functions) |
|
|
| - RTG (core poller code, pthreads, snmp, autoconf examples) |
|
|
| - Brady Alleman/Doug Warner (threading ideas, implementation details) |
|
|
+-------------------------------------------------------------------------+
|
|
| - Cacti - http://www.cacti.net/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
/* These functions handle simple signal handling functions for Spine. It was
|
|
written to handle specifically issues with the Solaris threading model in
|
|
version 2.8.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "spine.h"
|
|
|
|
/*! \fn static void signal_write(const char *text)
|
|
* \brief writes a NUL terminated message to stderr from a signal handler
|
|
*
|
|
*/
|
|
static void signal_write(const char *text) {
|
|
size_t length = 0;
|
|
|
|
while (text[length] != '\0') {
|
|
length++;
|
|
}
|
|
|
|
while (length > 0) {
|
|
ssize_t written = write(STDERR_FILENO, text, length);
|
|
|
|
if (written <= 0) {
|
|
if (written < 0 && errno == EINTR) {
|
|
continue;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
text += written;
|
|
length -= (size_t) written;
|
|
}
|
|
}
|
|
|
|
/*! \fn static void signal_write_number(long long value)
|
|
* \brief writes a number to stderr from a signal handler
|
|
*
|
|
*/
|
|
static void signal_write_number(long long value) {
|
|
char digits[24];
|
|
size_t index = sizeof(digits) - 1;
|
|
unsigned long long magnitude = (unsigned long long) value;
|
|
|
|
digits[index] = '\0';
|
|
|
|
do {
|
|
digits[--index] = (char) ('0' + (magnitude % 10));
|
|
magnitude /= 10;
|
|
} while (magnitude > 0 && index > 0);
|
|
|
|
signal_write(digits + index);
|
|
}
|
|
|
|
/*! \fn static void spine_signal_handler(int spine_signal)
|
|
* \brief interrupts the os default signal handler as appropriate.
|
|
*
|
|
* Only async-signal-safe calls belong here. A SIGSEGV usually follows heap
|
|
* corruption, so formatting a log timestamp with strftime would take the
|
|
* malloc arena lock from the fault handler and hang the poller instead of
|
|
* killing it. time() is on the POSIX async-signal-safe list, so the stamp is
|
|
* written as epoch seconds and the reader converts it.
|
|
*/
|
|
static void spine_signal_handler(int spine_signal) {
|
|
const char *message = NULL;
|
|
int saved_errno = errno;
|
|
|
|
/* Every signal handled here except SIGSEGV, which exits with status 1
|
|
* below, resets to SIG_DFL and is raised again before returning, so the
|
|
* process ends with that signal's default action and core. Returning alone only ended real faults, which re-execute; after
|
|
* SIGINT, SIGQUIT or a sent SIGBUS the poller logged FATAL and kept going.
|
|
* SIGPIPE is a routine, recurring condition instead, and signal
|
|
* dispositions are process-wide: dropping it to SIG_DFL here, even
|
|
* briefly, would let a broken-pipe write on any other thread terminate
|
|
* the process during that window. Leave this handler permanently
|
|
* installed for SIGPIPE. */
|
|
if (spine_signal != SIGPIPE) {
|
|
signal(spine_signal, SIG_DFL);
|
|
}
|
|
|
|
set.exit.exit_code = spine_signal;
|
|
|
|
switch (spine_signal) {
|
|
case SIGABRT:
|
|
message = "FATAL: Spine Interrupted by Abort Signal\n";
|
|
break;
|
|
case SIGINT:
|
|
message = "FATAL: Spine Interrupted by Console Operator\n";
|
|
break;
|
|
case SIGSEGV:
|
|
message = "FATAL: Spine Encountered a Segmentation Fault\n";
|
|
break;
|
|
case SIGBUS:
|
|
message = "FATAL: Spine Encountered a Bus Error\n";
|
|
break;
|
|
case SIGFPE:
|
|
message = "FATAL: Spine Encountered a Floating Point Exception\n";
|
|
break;
|
|
case SIGQUIT:
|
|
message = "FATAL: Spine Encountered a Keyboard Quit Command\n";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
signal_write("[");
|
|
signal_write_number((long long) time(NULL));
|
|
signal_write("] ");
|
|
|
|
if (message != NULL) {
|
|
signal_write(message);
|
|
} else {
|
|
signal_write("FATAL: Spine Encountered An Unhandled Exception Signal Number: '");
|
|
signal_write_number(spine_signal);
|
|
signal_write("'\n");
|
|
}
|
|
|
|
if (spine_signal == SIGSEGV) {
|
|
_exit(1);
|
|
}
|
|
|
|
/* The signal stays blocked until this handler returns, then the default
|
|
* action ends the process. raise() is async-signal-safe. */
|
|
if (spine_signal != SIGPIPE) {
|
|
(void) raise(spine_signal);
|
|
}
|
|
|
|
errno = saved_errno;
|
|
}
|
|
|
|
static int spine_fatal_signals[] = {
|
|
SIGINT,
|
|
SIGSEGV,
|
|
SIGBUS,
|
|
SIGFPE,
|
|
SIGQUIT,
|
|
SIGSYS,
|
|
SIGABRT,
|
|
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.
|
|
*
|
|
*/
|
|
void install_spine_signal_handler(void) {
|
|
/* Set a handler for any fatal signal not already handled */
|
|
int i;
|
|
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) {
|
|
sa.sa_handler = spine_signal_handler;
|
|
sigemptyset(&sa.sa_mask);
|
|
sa.sa_flags = SA_RESTART;
|
|
sigaction(spine_fatal_signals[i], &sa, NULL);
|
|
}
|
|
}
|
|
|
|
for (i=0; spine_fatal_signals[i]; ++i) {
|
|
ohandler = signal(spine_fatal_signals[i], spine_signal_handler);
|
|
if (ohandler != SIG_DFL) {
|
|
signal(spine_fatal_signals[i], ohandler);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*! \fn void uninstall_spine_signal_handler(void)
|
|
* \brief uninstalls the spine signal handler.
|
|
*
|
|
*/
|
|
void uninstall_spine_signal_handler(void) {
|
|
/* Remove a handler for any fatal signal handled */
|
|
int i;
|
|
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) {
|
|
sa.sa_handler = SIG_DFL;
|
|
sigaction(spine_fatal_signals[i], &sa, NULL);
|
|
}
|
|
}
|
|
|
|
for ( i=0; spine_fatal_signals[i]; ++i ) {
|
|
ohandler = signal(spine_fatal_signals[i], SIG_DFL);
|
|
if (ohandler != spine_signal_handler) {
|
|
signal(spine_fatal_signals[i], ohandler);
|
|
}
|
|
}
|
|
}
|