Protect v2 journal populate walk from SIGBUS (#22514)
* fix(dbengine): protect v2 journal populate walk from SIGBUS journalfile_v2_populate_retention_to_mrg() walked the mmap'd v2 journal in two phases: an optional CRC check (when JOURNALFILE_FLAG_METRIC_CRC_CHECK is set -- the default cheap-load path on every startup) followed by the mrg update. Only the mrg update ran inside PROTECTED_ACCESS_SETUP. If a backing page could not be paged in (file truncated, sparse hole, transient I/O error) the CRC walk hit SIGBUS and the process aborted with no recovery handler armed. Move PROTECTED_ACCESS_SETUP above the CRC check so one region covers both phases. On signal recovery, clear JOURNALFILE_FLAG_IS_AVAILABLE so the next pass rebuilds the journal -- same effect as a CRC failure. Drop the bare early-return on CRC failure, which leaked the acquire refcount, and fall through to the single journalfile_v2_data_release at the end. Mirrors the pattern in journalfile_v2_validate() at the load site. * fix(dbengine): ensure clean teardown of v2 journal on failure Add immediate unmap of fd/mmap and cleanup of njfv2idx entry when v2 journals fail during critical paths. Prevents leaving journals marked unavailable but partially initialized, which could lead to fd/mmap overwrite and duplicate index entries during rebuilds. * fix(dbengine): validate v2 journal header offsets to prevent over-read and ensure clean failure handling Add bounds checks for header-controlled offsets against the mmap size to prevent corruption-driven over-reads. Centralize teardown logic by clearing IS_AVAILABLE under a spinlock and unmapping permanently, avoiding dangling index entries or fd/mmap leaks during rebuild. * fix(dbengine): improve v2 header validation to prevent underflow and size wrapping Refine bounds checks for metric offsets and metric count in v2 journal headers. Avoid underflow and prevent size_t wrapping on 32-bit builds by reordering checks and using division instead of multiplication for capacity validation. * fix(dbengine): enhance v2 journal validation and clarify teardown logic Add stricter checks for trailer offsets to detect malformed v2 journal headers and prevent corrupted metric lists. Adjust header-controlled size bounds in CRC checks for future-proofing. Refactor IS_AVAILABLE clearing and refcount release sequence for robust teardown, avoiding dangling mappings or index entries. * protect(dbengine): safeguard v2 journal retention updates from SIGBUS Add PROTECTED_ACCESS_SETUP to safely handle mmap walks during metric retention updates. Ensure clean failure handling and resource release in case of journal access errors. * fix(dbengine): mark volatile variables in v2 journal access to ensure safety after recovery Declare uuid_first_entry_list, count, and added as volatile to maintain well-defined behavior across setjmp/longjmp in PROTECTED_ACCESS_SETUP regions. * protect(dbengine): safeguard v2 journal size statistics walk from SIGBUS Add PROTECTED_ACCESS_SETUP to handle mmap walks during the collection of size statistics. Ensure clean failure handling and resource release in case of journal access errors. * fix(dbengine): initialize `has_references` to false in v2 journal setup Ensure `has_references` is explicitly set to false during v2 journal initialization for consistent reference tracking. * protect(dbengine): validate v2 journal bounds during size stats walk Add detailed bounds checks for extent and metric lists in v2 journals to prevent over-reads and ensure safe traversal. Enhance failure handling with consistent resource release on invalid mappings. * fix(dbengine): use uint8_t* for v2 journal byte arithmetic populate_v2_statistics() declared data_start as void* and walked the mmap'd v2 journal with `(void *)(data_start + offset)` pointer arithmetic. Pointer arithmetic on void* is a GCC extension, undefined under standards-strict semantics, and CodeQL's cpp/suspicious-pointer-scaling-void rule flags every such site (alerts 3315, 3317-3320 on this PR). Declaring data_start as uint8_t* makes the arithmetic well-defined (1 byte per step, exactly what the GCC void* extension produced) and silences the 5 alerts at no semantic cost. The existing (void *) casts on the rvalues compile unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(dbengine): mark `journal_access_failed` as volatile and extend bounds checks Declare `journal_access_failed` as volatile to ensure safety in recovery scenarios and prevent future invariance breaks. Add checks to avoid overflow in uuid entry list size computations during v2 journal setup. * protect(dbengine): isolate UUID stack copies to prevent SIGBUS during mmap access Safeguard `journalfile` and `rrdengine` operations by copying UUIDs to the stack before invoking mrg functions. Ensures clean recovery in protected regions on unreadable backing pages and avoids lock state corruption during SIGBUS errors. * fix(dbengine): improve v2 header validation and clarify static analysis intent Enhance error logging for out-of-range v2 journal header offsets, enabling clearer diagnostics during rebuild scenarios. Add a comment clarifying tautological bounds for `descr->type` to assist static analyzers. * protect(dbengine): scope protected frame tightly in v2 journal retention updates Reduce nesting depth and mask of unrelated faults by tightly scoping the PROTECTED_ACCESS_SETUP frame to mmap walks only. Ensure proper cleanup and avoid inflated nesting in subsequent operations. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>