mirror of
https://github.com/Cacti/spine.git
synced 2026-11-03 18:48:13 +09:00
## Summary Consolidates the connector/build work from #613 and the broader test-infrastructure work extracted from #597 into one review: - discover MariaDB Connector/C first while retaining MySQL and Percona-compatible support - test MariaDB and MySQL Connector/C with strict GCC and Clang builds on Ubuntu 24.04 - add a real macOS 15 build and unit-test lane with portable Autotools platform detection - run unit tests under ASan/UBSan and ThreadSanitizer - enforce the measured 7% global coverage baseline and 70% changed-line coverage - add libFuzzer coverage for output parsing alongside the existing hostname parser - exercise the SNMPv3 integration suite against MariaDB and MySQL 8.4 - add cppcheck, shellcheck, DCO verification, CODEOWNERS, distcheck, and Linux-capability checks while retaining the repository-managed default CodeQL setup - expose a stable `CI / required` aggregate check for branch protection - pin runners and third-party actions and keep workflow permissions read-only by default ## Correctness fixes found by the new gates - reject truncated capability strings and poller-output SQL tuples instead of publishing partial data - make `bootstrap` fail closed and select `libtoolize`/`glibtoolize` portably - use Autoconf's canonical host for Darwin detection instead of an empty native-build alias - remove stale initialization/name-shadow findings and make read-only interfaces const-correct - make the ICMP parser test portable across Linux and BSD header names ## Verification - strict GCC and Clang builds: 4/4 unit targets passed - ASan/UBSan: 4/4 passed - ThreadSanitizer: 4/4 passed - macOS Clang/Homebrew build: 4/4 passed - MariaDB integration: passed - MySQL 8.4 integration: passed - both libFuzzer targets: passed sustained local runs - coverage: 7.2% global and 84% on changed lines - cppcheck, shellcheck, actionlint, DCO verification, and `git diff --check`: passed locally ## Post-merge repository settings After these workflows exist on `develop`, an administrator should require `CI / required`, Fuzz, Integration, Rocky Linux, and the repository-managed CodeQL/code-scanning result in the active ruleset; enable code-owner approval; and require the branch to be current (or use a merge queue). Enabling those settings before this PR merges would create required checks that the base branch cannot yet emit. Consolidates and supersedes #613. Closes #612. --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> Signed-off-by: TheWitness <thewitness@cacti.net> Signed-off-by: TheWitness <copilot@users.noreply.github.com> Co-authored-by: TheWitness <thewitness@cacti.net> Co-authored-by: TheWitness <copilot@users.noreply.github.com>
154 lines
4.9 KiB
Bash
Executable File
154 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Integration test for the startup allocation guards (issue#564).
|
|
#
|
|
# php_processes and debug_devices are dereferenced on the line after their
|
|
# calloc(), so an allocation failure there used to be a NULL deref rather than
|
|
# a diagnosable exit. A unit test cannot reach them: they live in main().
|
|
# This injects the failure into the real binary with an LD_PRELOAD calloc that
|
|
# returns NULL on the Nth call, and asserts spine dies with its own message.
|
|
#
|
|
# The db_pool_local and db_pool_remote guards are not covered here. They run
|
|
# after the database connection, so reaching them needs the SNMPv3 harness's
|
|
# MySQL container rather than a bare binary.
|
|
#
|
|
# Skips with exit 77 (automake and prove read that as "skipped") when the
|
|
# binary, a compiler, or LD_PRELOAD interposition is unavailable, so the
|
|
# suite stays green on platforms where this technique does not apply.
|
|
#
|
|
# Usage: ./tests/integration/test_alloc_failure.sh
|
|
set -euo pipefail
|
|
|
|
if [[ "$(uname -s)" != "Linux" ]]; then
|
|
echo "LD_PRELOAD allocation interposition is only supported by this test on Linux; skipping"
|
|
exit 77
|
|
fi
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
PASS=0
|
|
FAIL=0
|
|
|
|
pass() { echo " PASS: $*"; PASS=$((PASS+1)); }
|
|
fail() { echo " FAIL: $*"; FAIL=$((FAIL+1)); }
|
|
|
|
SPINE=""
|
|
if [[ -n "${top_builddir:-}" && -x "$top_builddir/spine" ]]; then
|
|
SPINE="$top_builddir/spine"
|
|
elif [[ -x "$REPO_ROOT/spine" ]]; then
|
|
SPINE="$REPO_ROOT/spine"
|
|
elif command -v spine >/dev/null 2>&1; then
|
|
SPINE="$(command -v spine)"
|
|
fi
|
|
|
|
if [[ -z "$SPINE" ]]; then
|
|
echo "no spine binary found; skipping"
|
|
exit 77
|
|
fi
|
|
|
|
if [[ -u "$SPINE" ]]; then
|
|
echo "the spine binary is setuid and the loader will ignore LD_PRELOAD; skipping"
|
|
exit 77
|
|
fi
|
|
|
|
# ASan/UBSan/TSan install their own allocator interceptors ahead of anything
|
|
# LD_PRELOAD can add, so a preloaded calloc() never sees (and cannot fail)
|
|
# the process's real allocations under those runtimes. Detect a sanitized
|
|
# binary by its exported runtime-init symbols and skip rather than silently
|
|
# testing nothing (or, worse, forcing the sanitizer runtime's own bootstrap
|
|
# allocation to fail and hang instead of exercising the guard at all).
|
|
#
|
|
# The symbol table is captured into a variable before grep runs against it
|
|
# (rather than piping straight into `grep -q`) because grep -q exits as soon
|
|
# as it finds a match, which under `set -o pipefail` can SIGPIPE the still-
|
|
# writing nm/strings process and make the pipeline look like it failed even
|
|
# though the match was found.
|
|
if command -v nm >/dev/null 2>&1; then
|
|
spine_symbols="$(nm -D "$SPINE" 2>/dev/null || true)"
|
|
elif command -v strings >/dev/null 2>&1; then
|
|
spine_symbols="$(strings "$SPINE" 2>/dev/null || true)"
|
|
else
|
|
spine_symbols=""
|
|
fi
|
|
|
|
if grep -qE '__asan_init|__tsan_init|__msan_init|__ubsan_(handle|default_options)' <<<"$spine_symbols"; then
|
|
echo "the spine binary is built with a sanitizer runtime, which takes over calloc() ahead of LD_PRELOAD; skipping"
|
|
exit 77
|
|
fi
|
|
|
|
if ! command -v cc >/dev/null 2>&1; then
|
|
echo "no compiler available for the interposer; skipping"
|
|
exit 77
|
|
fi
|
|
|
|
WORK="$(mktemp -d)"
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
|
|
cat > "$WORK/failcalloc.c" <<'INTERPOSER'
|
|
#define _GNU_SOURCE
|
|
#include <dlfcn.h>
|
|
#include <stdlib.h>
|
|
|
|
/* Return NULL from the Nth calloc() of the process, pass the rest through. */
|
|
static long seen;
|
|
static long fail_at = -1;
|
|
|
|
void *calloc(size_t nmemb, size_t size) {
|
|
static void *(*real_calloc)(size_t, size_t);
|
|
|
|
if (real_calloc == NULL) {
|
|
const char *at;
|
|
|
|
real_calloc = dlsym(RTLD_NEXT, "calloc");
|
|
at = getenv("SPINE_FAIL_CALLOC_AT");
|
|
|
|
if (at != NULL) {
|
|
fail_at = atol(at);
|
|
}
|
|
}
|
|
|
|
if (++seen == fail_at) {
|
|
return NULL;
|
|
}
|
|
|
|
return real_calloc(nmemb, size);
|
|
}
|
|
INTERPOSER
|
|
|
|
if ! cc -shared -fPIC -o "$WORK/failcalloc.so" "$WORK/failcalloc.c" -ldl 2>/dev/null; then
|
|
echo "could not build the LD_PRELOAD interposer; skipping"
|
|
exit 77
|
|
fi
|
|
|
|
# Sanity check: without injection --help must still work, otherwise the
|
|
# interposer itself is broken and every assertion below would be meaningless.
|
|
if ! LD_PRELOAD="$WORK/failcalloc.so" "$SPINE" --help >/dev/null 2>&1; then
|
|
echo "LD_PRELOAD interposition not supported here; skipping"
|
|
exit 77
|
|
fi
|
|
|
|
echo "Allocation failure guards:"
|
|
|
|
# Shared-library constructors can call calloc before main(), so the process-
|
|
# wide ordinal is deliberately discovered rather than assumed. A matching
|
|
# diagnostic proves the guarded allocation itself was the one that failed.
|
|
check_guard() {
|
|
local want="$1" nth out
|
|
|
|
for nth in $(seq 1 256); do
|
|
out="$(SPINE_FAIL_CALLOC_AT="$nth" LD_PRELOAD="$WORK/failcalloc.so" "$SPINE" --help 2>&1 || true)"
|
|
if grep -q "Fatal calloc error: spine.c $want" <<<"$out"; then
|
|
pass "failing $want allocation exits through its guard (calloc #$nth)"
|
|
return
|
|
fi
|
|
done
|
|
|
|
fail "could not reach the $want allocation guard in the first 256 calloc calls"
|
|
}
|
|
|
|
check_guard php_processes
|
|
check_guard debug_devices
|
|
|
|
echo
|
|
echo "passed: $PASS, failed: $FAIL"
|
|
[[ "$FAIL" -eq 0 ]]
|