Skip to content

Fix memory-safety and correctness bugs surfaced by Coverity audit (part 3) - #22268

Merged
stelfrag merged 9 commits into
netdata:masterfrom
stelfrag:cov_fix_part3
Apr 25, 2026
Merged

Fix memory-safety and correctness bugs surfaced by Coverity audit (part 3)#22268
stelfrag merged 9 commits into
netdata:masterfrom
stelfrag:cov_fix_part3

Conversation

@stelfrag

@stelfrag stelfrag commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator
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.

  • Bug Fixes
    • Thread-safety: atomic cancel flag in ACLK; lock StatsD histogram buffer for reset/growth/append/flush; snapshot receiver status under lock.
    • Correctness: fix mdstat obsolete chart lookup by matching type/id; avoid UAF in power-supply property loop; skip Xen domains when libxl_domain_info() fails.
    • Robustness: grow cpuset CPU parser buffer and handle system_cpus==0; unlock apps_and_stdout_mutex before exit on print-tree path.
    • Time formatting: fix RFC3339 fractional scaling for 1–9 digits and add regression tests.

Written for commit 74208a3. Summary will update on new commits.

ktsaou added 9 commits April 24, 2026 19:55
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.
@stelfrag
stelfrag marked this pull request as ready for review April 24, 2026 17:01
Copilot AI review requested due to automatic review settings April 24, 2026 17:01
@stelfrag
stelfrag marked this pull request as draft April 24, 2026 17:01

@cubic-dev-ai cubic-dev-ai Bot 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.

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 when count is set but used == 0, which can leave stale last_* 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 the count/used == 0 path 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
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/collectors/statsd.plugin/statsd.c

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.

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.

@sonarqubecloud

Copy link
Copy Markdown

@stelfrag
stelfrag marked this pull request as ready for review April 24, 2026 18:30

@thiagoftsm thiagoftsm 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.

Everything ran as expected with this PR as well. LGTM!

@stelfrag
stelfrag merged commit eee9c55 into netdata:master Apr 25, 2026
161 of 162 checks passed
@stelfrag
stelfrag deleted the cov_fix_part3 branch April 25, 2026 13:03
@stelfrag stelfrag mentioned this pull request Jun 22, 2026
Ferroin pushed a commit that referenced this pull request Jul 15, 2026
…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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants