Fix memory-safety and correctness bugs surfaced by Coverity audit (part 3) - #22268
Conversation
Coverity CID 413854 (UNINIT): skip the current domain when `libxl_domain_info()` fails before hashing or storing its UUID. This avoids using an uninitialized `uuid` buffer on the error path in `xenstat_collect()`.
Coverity CID 457729 (INTEGER_OVERFLOW): print_fraction() underflowed its loop bound when callers requested 7-9 fractional digits. Scale microseconds down for shorter output and pad zeros for 7-9 digits so the formatter preserves the existing 1..9-digit contract without hanging.
Coverity CID 425864 (TAINTED_SCALAR): os_read_cpuset_cpus() sized its static buffer from the first caller's system_cpus argument, and startup can first call it with 0. Derive a non-zero CPU baseline when needed and grow the buffer before reading cpuset.cpus so long CPU lists are not truncated into wrong counts or out-of-bounds parsing.
Chart creation in proc_mdstat.c builds the short chart id "<raid>_<suffix>"
and passes it to rrdset_create_localhost("mdstat", id, ...); the RRD layer
later prefixes "mdstat." when constructing the full chart id.
make_chart_obsolete() was instead formatting the full "mdstat.<raid>_<suffix>"
directly into a 50-byte buffer and calling rrdset_find_active_byname_localhost,
which means the two paths used different truncation boundaries. For long but
valid md names (mdadm(8) allows up to 32 characters), the create-path chart
was longer than what obsolete-path could build, so the lookup missed and the
array's "availability" chart was never marked obsolete.
Build the same short chart id in the obsolete path and resolve it through
the type/id lookup helper so long valid md names match the created chart.
Related: Coverity CID 414643 flagged an OVERRUN on this line; the trace is
a tool-model false positive (no OOB in the current code), but investigating
it surfaced the real correctness bug fixed here.
Coverity CID 348628 (USE_AFTER_FREE): do_sys_class_power_supply() could free the current power supply while iterating its property list, then evaluate the outer loop increment on the freed property node. Store the next property before the inner loop and stop iterating once the error path frees the power supply.
Coverity CID 410067 (MISSING_LOCK): rrdhost_status_ingest() read receiver status fields without receiver_lock while the receiver thread updated the same state under that lock. Snapshot the receiver status block under the lock before deriving ingest status so status reporting no longer races with connect and disconnect updates.
Coverity CID 410087 (MISSING_LOCK): aclk_web_client_interrupt_cb() read pending_req_list.canceled from a libuv worker while cancel paths updated the same flag from the ACLK side. Switch the cancel flag accesses to atomic load/store so query cancellation stays lock-free on the hot callback path without racing across threads.
Coverity CID 410124 (MISSING_LOCK): histogram and timer samples updated the shared buffer partly outside `m->histogram.ext->mutex` while the flush path sorted the same buffer under that lock. Keep reset, growth, append, and flush snapshot on the same mutex so samples are not dropped or read from a partially synchronized buffer.
Coverity CID 381151 (LOCK): the stored trace points to stale line numbers, but the current print-tree exit path still called exit() while holding apps_and_stdout_mutex. Unlock the mutex after printing and before exit() so the module destructor does not destroy a locked uv_mutex_t.
There was a problem hiding this comment.
1 issue found across 10 files
Confidence score: 3/5
- There is a moderate regression risk in
src/collectors/statsd.plugin/statsd.c: the new guard may skip both histogram recomputation and zeroing whencountis set butused == 0, which can leave stalelast_*values visible. - I’m scoring this as a 3 because the issue is medium severity (6/10) with reasonable confidence (6/10) and it affects runtime behavior rather than just code style or maintainability.
- Pay close attention to
src/collectors/statsd.plugin/statsd.c- verify the guard still clears or recomputes histogram state in thecount/used == 0path to avoid stale metrics.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/collectors/statsd.plugin/statsd.c">
<violation number="1" location="src/collectors/statsd.plugin/statsd.c:1982">
P2: The new guard can skip both histogram recomputation and zeroing when `count` is set but `used == 0`, leaving stale `last_*` values.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Cloud as ACLK / Cloud
participant Client as StatsD Client
participant StatsD as StatsD Collector
participant Plugins as Apps/Proc/Xen Plugins
participant RRD as RRD Engine / Host
participant OS as OS (Kernel/Libxl)
Note over Cloud, RRD: ACLK Request Management
Cloud->>Cloud: Request Cancellation
Cloud->>Cloud: NEW: Atomic store to canceled flag
Cloud->>Cloud: NEW: Atomic load in interrupt callback
Note over Client, StatsD: Thread-Safe Metric Ingestion
Client->>StatsD: Send Histogram/Timer data
StatsD->>StatsD: NEW: Lock metric histogram mutex
alt Buffer Full
StatsD->>StatsD: CHANGED: Resize value buffer (reallocz)
end
StatsD->>StatsD: Append metric value
StatsD->>StatsD: NEW: Unlock histogram mutex
Note over StatsD, RRD: Metric Flushing
StatsD->>StatsD: NEW: Lock histogram mutex
StatsD->>StatsD: Calculate min/max/percentiles/stddev
StatsD->>RRD: Update RRDSET
StatsD->>StatsD: NEW: Unlock histogram mutex
Note over Plugins, OS: Resource-Safe Data Collection
Plugins->>OS: Read /proc or /sys properties
alt Error detected (e.g. read failure)
Plugins->>Plugins: CHANGED: Safely free resources (e.g. power_supply_free)
Plugins->>Plugins: NEW: Break loop/Continue to avoid UAF
else Success
Plugins->>RRD: CHANGED: Lookup chart by Type/ID (mdstat)
end
Note over Plugins, RRD: Thread-Safe Status Monitoring
RRD->>RRD: Request Host Ingest Status
RRD->>RRD: NEW: Lock host receiver status
RRD->>RRD: Snapshot last_connected/disconnected
RRD->>RRD: NEW: Unlock host receiver status
RRD-->>RRD: Calculate status (Online/Offline/Archived)
Note over Plugins, OS: Process Hierarchy Cleanup
Plugins->>OS: Read process tree
opt print_tree_and_exit
Plugins->>OS: Print hierarchy
Plugins->>Plugins: NEW: Unlock apps_and_stdout_mutex before exit
end
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Pull request overview
This PR continues the Coverity-audit remediation series by addressing several memory-safety, concurrency, and correctness issues across core components (CPU/cgroup parsing, RFC3339 formatting, collectors, ACLK, and host status reporting).
Changes:
- Fix cpuset CPU parsing buffer sizing and RFC3339 fractional formatting, and add regression unit tests for the formatter.
- Harden multiple collectors and status paths against races/UAF/uninitialized reads (receiver status snapshotting, statsd histogram mutexing, xenstat domain-info failures, power-supply property iteration).
- Correct operational edge cases (unlock on early exit in
apps.plugin, atomic cancel flag in ACLK pending request list, and mdstat obsolete-chart lookup by type/id).
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/libnetdata/os/get_system_cpus.c |
Grow/reuse cpuset parsing buffer based on detected CPU count; avoid undersized static buffer. |
src/libnetdata/json/json-c-parser-unittest.c |
Add RFC3339 formatter regression tests (3/7/9 fractional digits + round-trip parse). |
src/libnetdata/datetime/rfc3339.c |
Fix fractional scaling logic for 1–9 digits (truncate/pad correctly) with safer validation flow. |
src/database/rrdhost-status.c |
Snapshot receiver status under rrdhost_receiver_lock() to avoid racy reads. |
src/collectors/xenstat.plugin/xenstat_plugin.c |
Skip domains when libxl_domain_info() fails to avoid using uninitialized UUID data. |
src/collectors/statsd.plugin/statsd.c |
Protect histogram/timer sample buffer reset/grow/append/flush with the metric mutex. |
src/collectors/proc.plugin/sys_class_power_supply.c |
Prevent UAF when freeing a power supply while iterating its properties. |
src/collectors/proc.plugin/proc_mdstat.c |
Obsolete mdstat charts using type+id lookup (consistent with chart creation). |
src/collectors/apps.plugin/apps_plugin.c |
Release apps_and_stdout_mutex before exit(0) on print_tree_and_exit. |
src/aclk/aclk_query.c |
Use atomic load/store for the pending-request canceled flag read from an interrupt callback. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
thiagoftsm
left a comment
There was a problem hiding this comment.
Everything ran as expected with this PR as well. LGTM!
…rt 3) (#22268) * xenstat: skip domains without libxl domain info Coverity CID 413854 (UNINIT): skip the current domain when `libxl_domain_info()` fails before hashing or storing its UUID. This avoids using an uninitialized `uuid` buffer on the error path in `xenstat_collect()`. * datetime: fix rfc3339 fractional scaling Coverity CID 457729 (INTEGER_OVERFLOW): print_fraction() underflowed its loop bound when callers requested 7-9 fractional digits. Scale microseconds down for shorter output and pad zeros for 7-9 digits so the formatter preserves the existing 1..9-digit contract without hanging. * libnetdata: grow cpuset cpu parser buffer Coverity CID 425864 (TAINTED_SCALAR): os_read_cpuset_cpus() sized its static buffer from the first caller's system_cpus argument, and startup can first call it with 0. Derive a non-zero CPU baseline when needed and grow the buffer before reading cpuset.cpus so long CPU lists are not truncated into wrong counts or out-of-bounds parsing. * proc: fix mdstat obsolete chart lookup key mismatch Chart creation in proc_mdstat.c builds the short chart id "<raid>_<suffix>" and passes it to rrdset_create_localhost("mdstat", id, ...); the RRD layer later prefixes "mdstat." when constructing the full chart id. make_chart_obsolete() was instead formatting the full "mdstat.<raid>_<suffix>" directly into a 50-byte buffer and calling rrdset_find_active_byname_localhost, which means the two paths used different truncation boundaries. For long but valid md names (mdadm(8) allows up to 32 characters), the create-path chart was longer than what obsolete-path could build, so the lookup missed and the array's "availability" chart was never marked obsolete. Build the same short chart id in the obsolete path and resolve it through the type/id lookup helper so long valid md names match the created chart. Related: Coverity CID 414643 flagged an OVERRUN on this line; the trace is a tool-model false positive (no OOB in the current code), but investigating it surfaced the real correctness bug fixed here. * proc.plugin: avoid uaf in power supply property loop Coverity CID 348628 (USE_AFTER_FREE): do_sys_class_power_supply() could free the current power supply while iterating its property list, then evaluate the outer loop increment on the freed property node. Store the next property before the inner loop and stop iterating once the error path frees the power supply. * rrdhost: fix unlocked receiver status snapshot Coverity CID 410067 (MISSING_LOCK): rrdhost_status_ingest() read receiver status fields without receiver_lock while the receiver thread updated the same state under that lock. Snapshot the receiver status block under the lock before deriving ingest status so status reporting no longer races with connect and disconnect updates. * aclk: fix pending request cancellation race Coverity CID 410087 (MISSING_LOCK): aclk_web_client_interrupt_cb() read pending_req_list.canceled from a libuv worker while cancel paths updated the same flag from the ACLK side. Switch the cancel flag accesses to atomic load/store so query cancellation stays lock-free on the hot callback path without racing across threads. * statsd: lock histogram sample buffer access Coverity CID 410124 (MISSING_LOCK): histogram and timer samples updated the shared buffer partly outside `m->histogram.ext->mutex` while the flush path sorted the same buffer under that lock. Keep reset, growth, append, and flush snapshot on the same mutex so samples are not dropped or read from a partially synchronized buffer. * apps.plugin: unlock mutex before print exit Coverity CID 381151 (LOCK): the stored trace points to stale line numbers, but the current print-tree exit path still called exit() while holding apps_and_stdout_mutex. Unlock the mutex after printing and before exit() so the module destructor does not destroy a locked uv_mutex_t. --------- Co-authored-by: Costa Tsaousis <costa@netdata.cloud> (cherry picked from commit eee9c55)



Summary
Summary by cubic
Fixes memory-safety and correctness issues found by Coverity across collectors, core, and libs. Improves thread-safety for cancellation and StatsD histograms, fixes chart lookup and domain handling, and adds RFC3339 formatting tests.
libxl_domain_info()fails.system_cpus==0; unlockapps_and_stdout_mutexbefore exit on print-tree path.Written for commit 74208a3. Summary will update on new commits.