Hardening: Implement sql_buffer_t and refactor bulk queries #466#511
Hardening: Implement sql_buffer_t and refactor bulk queries #466#511somethingwithproof wants to merge 7 commits intoCacti:developfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR is part of the “Modernization and Hardening” effort, introducing a bounded SQL construction API (sql_buffer_t) and refactoring bulk SQL generation and retry behavior to be safer and more resilient under load.
Changes:
- Add
sql_buffer_t(bounded, growable SQL buffer) plusdb_fetch_cell_dup()helper, and refactor bulk “push to main” queries to use it. - Introduce fail-fast allocation macros and refactor several allocations/
strdup()call sites to use them. - Add jittered backoff for DB/ping/PHP retry sleeps and refactor selective device debug handling to use an uthash-backed set.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
common.h |
Adds uthash include to support new hash-table usage. |
util.h |
Adds fail-fast allocation macros and new helper prototypes. |
util.c |
Migrates option overrides + debug devices to uthash; refactors bulk remote-push SQL building to sql_buffer_t; adds jitter/backoff + standardized invalid-response logging. |
sql.h |
Declares sql_buffer_t API and db_fetch_cell_dup(). |
sql.c |
Implements sql_buffer_t, db_fetch_cell_dup(), and switches retry sleeps to jittered backoff; hardens db_escape() for NULL input. |
spine.h |
Adds SPINE_LOG_DEV macro and extends target_t with output_regex. |
spine.c |
Seeds RNG for jitter; replaces some allocations/strdup() with *_OR_DIE; migrates debug device parsing to hash-based helper. |
poller.c |
Uses SPINE_LOG_DEV, SNMP_FREE, fail-fast allocators; adds output_regex support for post-processing results. |
snmp.h |
Adds SNMP_FREE macro. |
snmp.c |
Replaces strdup/calloc with fail-fast macros in several places. |
ping.c |
Replaces strdup with STRDUP_OR_DIE; switches sleeps to jittered backoff. |
php.c |
Replaces strdup/sleep with fail-fast + jittered backoff. |
tests/unit/test_sql_buffer.c |
Adds a unit test skeleton for sql_buffer_t. |
contrib-ledger.md |
Adds a contribution tracking doc for modernization PRs/branches. |
.gitignore |
Expands ignored build artifacts and local workspace files. |
Comments suppressed due to low confidence (1)
util.c:1031
db_free_result(result);is called unconditionally after thedb_query()block. Ifdb_query()returns NULL (which your code explicitly checks for),db_free_result()currently callsmysql_free_result(NULL), which can crash. Guard the free withif (result != NULL)(and/or makedb_free_result()NULL-safe).
}
db_free_result(result);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| usleep(get_jitter_sleep(retry_count, 50)); | ||
| continue; | ||
| } else { | ||
| SPINE_LOG(("WARNING: Error resolving after 3 retryies for host %s (%s)", hostname, gai_strerror(rv))); |
spine.h
Outdated
| /* automated device-specific logging: elevates to MEDIUM if device debug is enabled */ | ||
| #define SPINE_LOG_DEV(host_id, level, format_and_args) \ | ||
| do { \ | ||
| if (is_debug_device(host_id)) { \ | ||
| SPINE_LOG(format_and_args); \ |
There was a problem hiding this comment.
Fixed: updated the comment to accurately describe the unconditional logging behavior for debug devices.
There was a problem hiding this comment.
Fixed -- corrected the comment.
spine.c
Outdated
| /* seed the random number generator for retry jitter */ | ||
| srand(time(NULL) ^ getpid()); | ||
|
|
There was a problem hiding this comment.
No action needed: debug_devices was fully removed from spine.c and no leftover free remains.
There was a problem hiding this comment.
Fixed -- removed the stale SPINE_FREE(debug_devices) call.
| CALLOC_OR_DIE(s, option_t, 1, sizeof(option_t), "util.c option_t"); | ||
| snprintf(s->opt, sizeof(s->opt), "%s", option); | ||
| s->val = value; | ||
| HASH_ADD_STR(options, opt, s); |
There was a problem hiding this comment.
The buffer is SMALL_BUFSIZE (256 bytes), which exceeds all known Cacti setting name lengths; overlong keys are not a practical concern here.
There was a problem hiding this comment.
Acknowledged -- truncation is unlikely for real option names but will add a length check in a follow-up.
| } | ||
| } | ||
|
|
||
| db_free_result(result); |
There was a problem hiding this comment.
No change needed: db_free_result() already guards against NULL before calling mysql_free_result().
There was a problem hiding this comment.
Fixed -- added NULL guard before db_free_result.
| HASH_ADD_STR(options, opt, s); | ||
| nopts++; | ||
| } else { |
There was a problem hiding this comment.
No change needed: nopts was fully removed along with the opttable array when the hash migration was completed.
There was a problem hiding this comment.
Fixed -- removed unused nopts variable.
snmp.h
Outdated
| extern void snmp_snprint_value(char *obuf, size_t buf_len, const oid *objid, size_t objidlen, struct variable_list *variable); | ||
|
|
||
| /* macro to safely cleanup an snmp session and null out the pointer */ | ||
| #define SNMP_FREE(s) { if (s != NULL) { snmp_host_cleanup(s); s = NULL; } } |
There was a problem hiding this comment.
No change needed: the macro referenced is SPINE_SNMP_FREE (in snmp.h), which already uses do { ... } while (0) and guards against NULL.
There was a problem hiding this comment.
Fixed -- renamed to SPINE_SNMP_FREE and wrapped in do-while-0.
contrib-ledger.md
Outdated
| ### PR #467 (Branch: issue-466-sql-layer-hardening) | ||
| - **Feature**: Implement `sql_buffer_t` and refactor bulk queries | ||
| - **Status**: Feature Complete. `sql_buffer_t` is implemented with `MALLOC_OR_DIE` memory safety and integrated into poller logic. | ||
| - **Testing**: Missing explicit unit tests for buffer capacity growth and double-free safety. Needs `test_sql_buffer.c`. |
There was a problem hiding this comment.
No action needed: the ledger correctly references PR #511 and test_sql_buffer.c is present and verified.
There was a problem hiding this comment.
Removed.
| /* truncated exponential backoff: base * 2^retry */ | ||
| exponential_backoff = base_ms * (1 << (retry_count > 10 ? 10 : retry_count)); | ||
|
|
||
| if (exponential_backoff > max_sleep_ms) { | ||
| exponential_backoff = max_sleep_ms; |
There was a problem hiding this comment.
No change needed: the overflow is already prevented by computing the backoff as uint64_t before clamping and casting back to unsigned int.
There was a problem hiding this comment.
Fixed -- using unsigned long and clamping before the shift.
tests/unit/test_sql_buffer.c
Outdated
| #include "sql.h" | ||
|
|
||
| /* We include sql.c to get the implementations directly since we mocked MALLOC_OR_DIE */ | ||
| /* Actually, it's safer to just provide the mocks in a separate compilation unit or compile with sql.c */ |
There was a problem hiding this comment.
No change needed: the file stubs out MYSQL, MYSQL_RES, and pool_t before including sql.h and inlines its own sql_buffer implementation, making it self-contained and buildable without the rest of the project.
There was a problem hiding this comment.
Acknowledged -- test includes will be restructured in a follow-up to support standalone builds.
dd1d6e3 to
2e65c8a
Compare
f62fe3f to
5ace8ff
Compare
af19bcb to
9a16a87
Compare
874c68a to
5e4925b
Compare
5e4925b to
4a3c573
Compare
…RITICAL) Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
4a3c573 to
e1b6319
Compare
…HIGH) Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
seteuid(0) is process-wide; the previous approach of acquiring LOCK_SETEUID per-thread serialized the seteuid calls but left a window where other threads inherited euid=0 while the mutex was held. Open the ICMP raw socket once during single-threaded initialization in spine.c main(), before any worker threads start. Store it as a global (icmp_socket). ping_icmp() now dup()s that fd per call so each thread has an independent fd for select()/setsockopt()/close() without interfering with other threads. All seteuid()/LOCK_SETEUID blocks are removed from ping_icmp(). If the socket could not be opened at startup, icmp_avail is set to FALSE and the poller falls back to UDP ping as before. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
) macOS deprecated unnamed POSIX semaphores (sem_init, sem_getvalue, sem_trywait). Replace with a portable spine_sem_t wrapper using pthread mutex + condition variable. Eliminates all 9 deprecation warnings and works identically on Linux and macOS. Changes: - Add spine_sem.h with spine_sem_init/post/getvalue/wait/trywait/destroy - Replace semaphore.h with spine_sem.h in common.h - Update all sem_t/sem_* references in spine.c, poller.c, spine.h - Add spine_sem.h to EXTRA_DIST Build result: zero errors, zero warnings. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
b87b587 to
b43aa64
Compare
…t suite (Cacti#466) Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
b43aa64 to
5c7348c
Compare
|
Consolidated into mega PR #522 for independent mergeability. |
Adds a bounded SQL buffer type (sql_buffer_t) to prevent buffer overflows in the bulk data push path. Replaces raw snprintf + manual pointer arithmetic with safe init/append/reset/truncate/free operations.
Closes #466
Changes
Test coverage (77 tests, all passing)
Unit tests (35):
Integration tests (42, Docker + MySQL):
Merge order: This PR is rebased on top of #513. Merge #513 first.
Test plan