Skip to content

fix: memory-safety and polling correctness backports for 1.2.x - #574

Open
somethingwithproof wants to merge 11 commits into
Cacti:1.2.xfrom
somethingwithproof:fix/memory-safety-1.2.x
Open

somethingwithproof wants to merge 11 commits into
Cacti:1.2.xfrom
somethingwithproof:fix/memory-safety-1.2.x

Conversation

@somethingwithproof

@somethingwithproof somethingwithproof commented Aug 31, 2026

Copy link
Copy Markdown
Member

Backports the memory-safety, process-lifecycle, and polling-correctness fixes identified in the 1.2.x review.

Memory safety and resource handling:

  • fix the strncopy one-byte overflow and keep its scan portable to older Solaris
  • reserve the php_readpipe terminator byte, enforce one aggregate response/startup deadline, and recycle a server whose partial or oversized response leaves unread protocol data
  • keep log newline appends inside flogmessage and initialize the buffer before strftime
  • free settings-query results on missing rows and check allocation results before dereference
  • preserve allocation ownership when trimming recache command output

Process and privilege lifecycle:

  • send quit to every PHP server before a shared grace period, then signal/reap in parallel rounds with bounded SIGTERM-to-SIGKILL escalation
  • normalize SIGCHLD to SIG_DFL, verify retained children with waitpid before signaling, and prevent active slots from overwriting unreaped PIDs
  • bound failed-server restart churn and make every no-server caller return U
  • block SIGPIPE only around complete script-server writes and consume only a newly generated pending signal
  • create pipe/socket descriptors close-on-exec, using pipe2/SOCK_CLOEXEC when available and a serialized fallback otherwise
  • serialize vfork, optional libc popen, and the complete process-wide euid-0 raw-socket window so data-input children cannot inherit root
  • remove unnecessary privilege elevation when closing raw sockets and fail closed if privileges cannot be dropped

Polling correctness:

  • preserve complete database hostnames, including bare IPv6 literals. The removed host:port parser was dead: callers do not consume its method/port outputs and overwrite those fields from the database, while tokenizing the hostname truncated valid IPv6 input.
  • validate IPv4 and ICMP header lengths before access, consume rejected peeked datagrams, and correlate echo replies by host, type, identifier, and sequence
  • make snmp_count stop on internal Net-SNMP errors, distinguish SNMPv1 noSuchName walk completion, and avoid turning internal errors into valid zero counts
  • fail closed when device/item-count rows cannot be fetched, preventing zero counts from becoming unbounded duplicate polls
  • make CLI fatal paths return failure and guard nullable database row fields

Verification:

  • ./bootstrap completes (using Homebrew glibtoolize; the initial unavailable GNU libtoolize probe is non-fatal)
  • default configure/build succeeds against Homebrew mysql-client, net-snmp, and OpenSSL 3
  • --enable-popen configure/build also succeeds
  • both builds use --enable-warnings and make -j4; remaining warnings are pre-existing macOS deprecations and unrelated unused variables
  • git diff --check passes
  • the blocking pre-push review gate passes all stages (critical/high, secondary scanners, medium, and missing-test review)

Scope notes:

  • The new close-on-exec handling covers descriptors created by the changed PHP/script/ICMP paths. Existing MySQL, log, and Net-SNMP descriptors do not yet have comprehensive close-on-exec coverage; that broader descriptor audit should be a separate change rather than implied by this backport.
  • In the non-default --enable-popen mode, the existing timeout path intentionally skips pclose and can retain a child until it exits. This PR secures that mode's fork against the euid window but does not redesign its legacy timeout ownership.
  • Spine has no in-tree C test harness. The failure-mode paths were statically reviewed and both supported build variants were compiled, but deployment validation should still exercise PHP restart/orphan handling, partial-response timeouts, SNMPv1 count behavior, and malformed/foreign ICMP replies.

…the buffer

When strlen(src) was at least obuf the length clamped to obuf and the
terminator went to dst[obuf], one past a buffer of exactly that size. The
pragma that suppressed the compiler's warning about it is no longer needed.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
read() was handed the whole remaining buffer, so a script server result of
exactly RESULTS_BUFFER bytes filled it and the terminator went one past the
end. The loop also had no guard for the case where no capacity was left.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
…st()

The copy used strlen(stack) as its size, and stack had just been zeroed, so
nothing was copied and strtok() saw an empty string. Every branch that parses
a transport prefix or port was unreachable as a result. strtok() also keeps one
process-wide save pointer while this runs on each poller thread.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
php_close() sent SIGTERM and moved on, so a script server that ignores it or
is stuck in uninterruptible I/O was left as an orphan. Shutdown now polls for
the child over a bounded window, escalates to SIGKILL, and polls again.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
rdb_ssl_key, rdb_ssl_cert and rdb_ssl_ca were BIG_BUFSIZE while the config
parser reads at most 255 characters into a BUFSIZE scratch buffer, so the
copy bound exceeded the source and the compiler said so. develop already
carries these at BUFSIZE.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
TheWitness
TheWitness previously approved these changes Aug 31, 2026
php_processes, debug_devices and both connection pools were dereferenced on
the next statement. Backport of the develop fix for issue#564.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Four settings helpers returned from the NULL-row branch without freeing while
every other exit frees. Backport of the develop fix for issue#566.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
strcat() wrote the newline at LOGSIZE-1 and its terminator one past the end
once the message filled the buffer. Backport of the develop fix for
issue#565, with the changelog entries for this batch.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
@somethingwithproof

Copy link
Copy Markdown
Member Author

Rolled in the 1.2.x half of three more bugs that are present on both branches, so this is now seven fixes rather than four. Still 0 warnings, matching the 1.2.x baseline.

  • issue#564: the four unchecked calloc() results in spine.c, dereferenced on the next statement
  • issue#565: strcat() wrote the log newline one byte past flogmessage once the message filled LOGSIZE
  • issue#566: MYSQL_RES leaked on the no-row branch of four settings helpers

The develop half of the same three is #576.

@somethingwithproof
somethingwithproof requested review from TheWitness and a lite review from Copilot and removed request for cigamit and netniV September 4, 2026 06:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are remaining correctness/portability issues in php.c (inconsistent BUFSIZE vs RESULTS_BUFFER bounds check and an unguarded usleep() relative to existing SOLAR_THREAD handling) that should be resolved before merging.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR backports several small, independent fixes from develop to the 1.2.x maintenance branch, primarily addressing memory-safety issues (off-by-one/overflow) and shutdown correctness in the PHP script-server path.

Changes:

  • Harden string/buffer handling (strncopy(), php_readpipe(), and log newline appending) to prevent out-of-bounds writes.
  • Improve correctness and thread-safety in hostname parsing (get_namebyhost() uses proper copy + strtok_r).
  • Ensure resources/processes are cleaned up (free MySQL result sets on empty fetch paths; reap script-server children on shutdown).
File summaries
File Description
util.c Fix result-set leaks in settings helpers; make spine_log() newline append bounded; fix strncopy() off-by-one/OOB behavior and remove warning-suppression pragmas.
spine.h Reduce remote DB SSL path buffers to BUFSIZE to match config parsing limits and avoid misleading copy bounds.
spine.c Add fatal checks for several calloc() allocations before dereference.
ping.c Fix hostname copying so parsing is reachable; switch to strtok_r for reentrant tokenization.
php.c Reserve NUL terminator space in php_readpipe(); add bounded shutdown/reap logic for script-server processes.
CHANGELOG Document the backported fixes for 1.2.32.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread php.c
Comment thread php.c Outdated
@somethingwithproof

Copy link
Copy Markdown
Member Author

@copilot Fix the code for all comments in this review thread.

When a review comment includes a suggested change, apply the suggestion exactly.

Do not make changes beyond what is described in the linked review thread.

RESULTS_BUFFER defaults to 2048 and BUFSIZE is 1024, so the second guard
rejected any reply over 1024 bytes that the read loop had already accepted,
and it did not break, so the next read overwrote the buffer it had just
declared out of range. The loop bound above it is the real limit.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Every other usleep() in php.c sits inside #ifndef SOLAR_THREAD.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
@somethingwithproof somethingwithproof changed the title fix: memory-safety and correctness backports for 1.2.x fix: memory-safety and polling correctness backports for 1.2.x Sep 5, 2026
@somethingwithproof
somethingwithproof force-pushed the fix/memory-safety-1.2.x branch 5 times, most recently from 477416b to 2579b34 Compare September 5, 2026 17:57
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
@somethingwithproof

Copy link
Copy Markdown
Member Author

Updated the consolidated backport through commit 9d05e4e after the full review cycle. The final pre-push verdict is PASS: all critical/high, secondary, medium, and missing-test scans are clean. I also built both the default nft_popen configuration and the supported --enable-popen configuration against Homebrew mysql-client, net-snmp, and OpenSSL 3. The PR body now documents the parser rationale, build evidence, and the remaining pre-existing descriptor/popen scope limitations.

@bmfmancini bmfmancini left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — reviewed the memory-safety and polling correctness backports (strncopy overflow fix, php_readpipe terminator handling, SIGCHLD/fork serialization, close-on-exec descriptors, ICMP validation, snmp_count fail-closed paths). Approving.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants