Files
spine/error.c
T
Thomas Vincent 898d8c692c fix(privileges): drop root before input and retain owned ICMP sockets (#639)
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>
2026-10-10 21:08:52 -04:00

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);
}
}
}