Misc fixes from Coverity and Sonar audits (cleanup batch) - #22299
Misc fixes from Coverity and Sonar audits (cleanup batch)#22299ktsaou wants to merge 23 commits into
Conversation
Coverity CID 503493 (DEADCODE): in send_curl_request(), the failure path of curl_slist_append() called curl_slist_free_all() through `if(headers)` -- but `headers` is initialized to NULL on entry and only assigned after the append succeeds. The condition can never be true and the free is unreachable. Drop the dead block. The remaining cleanup (curl_easy_cleanup, can_retry=false, return false) is unchanged and correct.
Sonar c:S3519 (BLOCKER): the reverse-scan loops in convert_cgroup_to_systemd_service() used `while (len--)` on a `size_t`, so when the input contained no separator the loop ran to completion and the post-decrement on len == 0 wrapped to SIZE_MAX. The subsequent `if (len)` was true and `s[len] = '\0'` wrote far out of bounds. The dot-search was the explicitly flagged path; the slash-search had the same wrap pattern but was accidentally benign because `&s[SIZE_MAX + 1]` wrapped back to `&s[0]`. Replace both reverse scans with strrchr() and an explicit non-NULL, non-leading check. Preserves the existing behavior for valid inputs (separator at index > 0 truncates / repositions; separator at index 0 or absent leaves the string unchanged) and removes the unsigned-underflow path.
There was a problem hiding this comment.
No issues found across 9 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Architecture diagram
sequenceDiagram
participant Client as Client Application
participant Service as IPC Service Layer
participant Transport as IPC Transport (SHM/UDS)
participant Kernel as OS Kernel / Filesystem
Note over Service,Kernel: Server Initialization & Recovery
Service->>Service: NEW: Strict server_init_raw() validation (null checks)
Service->>Transport: Create Server Endpoint
Transport->>Kernel: CHANGED: lstat() and open(O_NOFOLLOW)
Kernel-->>Transport: File/Socket metadata
alt Path exists but is stale
Transport->>Transport: CHANGED: Verify Inode matching
Transport->>Kernel: NEW: unlink_same_file() (atomic path guard)
else Path is active/ambiguous
Transport->>Transport: Treat as live (prevent overwrite)
end
Note over Client,Kernel: Connection & Negotiation
Client->>Service: Request Session Handshake
Service->>Service: NEW: header_payload_len() overflow checks
alt Valid Capacity
Service->>Service: CHANGED: Safe buffer allocation (ensure_buffer)
Service-->>Client: Accept (Negotiated Max Payload)
else Overflow/Limit Exceeded
Service-->>Client: Reject (NIPC_ERR_OVERFLOW)
end
Note over Client,Kernel: Data Exchange (Request/Response)
Client->>Service: Send Data Payload
Service->>Service: NEW: Validate size_t vs uint32_t (32-bit platform guards)
alt Payload size within limits
Service->>Transport: CHANGED: transport_send() with validated msg_len
Transport->>Kernel: Write to SHM / Socket
Kernel-->>Service: Success
else Payload too large
Service-->>Client: Return NIPC_ERR_LIMIT_EXCEEDED
end
Note over Service,Transport: Response Handling
Service->>Service: NEW: Post-dispatch response length validation
opt Response > negotiated limit
Service->>Service: NEW: server_note_response_capacity()
end
Sonar c:S3584 / c:S1763: spam_thread() returned directly when sendto() failed, leaking the strdup'd per-packet strings, the packets and lengths arrays, and the UDP socket fd. The cleanup at the end of the function sat after an unconditional `for (;;)` loop, so it was unreachable. Move the cleanup into the sendto() failure path (free each packets[j] string, then free packets, free lengths, close the socket) and drop the post-loop dead code. The function still has the same single exit path (early return on send failure); GCC recognises the trailing infinite loop and does not require a fall-through return.
…NG branch Sonar c:S935 (CRITICAL): aws_kinesis_connector_worker() is declared void *, but the conditional test-only early exit inside #ifdef UNIT_TESTING used a bare `return;`. Per C99/C11 6.8.6.4, a return without an expression is only permitted in a function whose return type is void. Production builds skip this block, but enabling UNIT_TESTING fails to compile. Change the bare return to `return NULL;` so test builds satisfy the function signature; production control flow is unchanged.
…anch Sonar c:S935 (CRITICAL): pubsub_connector_worker() is declared void *, but the conditional test-only early exit inside #ifdef UNIT_TESTING used a bare `return;`. Per C99/C11 6.8.6.4, a return without an expression is only permitted in a function whose return type is void. Production builds skip this block, but enabling UNIT_TESTING fails to compile. Same pattern as aws_kinesis.c:214 fixed in the previous commit.
…TING branch Sonar c:S935 (CRITICAL): exporting_main() is declared void, but the conditional test-only early exit inside #ifdef UNIT_TESTING used `return NULL;`, which is invalid for a void-returning function per C99/C11 6.8.6.4. Production builds skip the block, but enabling UNIT_TESTING fails to compile. Change the conditional return to a bare `return;`, and remove the stale "@return It always returns NULL" line from the function's docstring (left behind from when the signature was void *). Together with the previous two commits, this closes the c:S935 trio across the exporting subsystem (aws_kinesis, pubsub, exporting_engine).
Sonar c:S2612 (MAJOR vulnerability): the management API key file was created with `open(..., O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 444)`. The literal `444` is decimal, not octal, which equals octal 0674 = rw-rwxr--. Group gets read+write+execute and others get read on a file that stores the management API key (a UUID granting admin endpoint access). Anyone with local read on the host can lift the key. Even if the original intent was octal `0444` (world-readable), a secret-key file should not be readable by group or others. Use `0600` (owner read+write only), the standard mode for secrets.
Sonar c:S2612: mark_database_to_recover() created the .netdata-meta.db.recover / .delete marker with `open(..., O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 444)`. The literal `444` is decimal, which is octal 0o674 (rw-rwxr--) -- group rwx + others r. Same decimal-not-octal typo as the previous api_v1_manage.c commit. The file is a marker created and immediately closed (no body written); only its existence matters at next-startup recovery detection. It does not need group or other access. Use mode 0600 -- minimum permissions needed by the owning netdata process, eliminates the c:S2612 violation.
Sonar c:S2612: file_lock_get() created the advisory lock file with mode 0666 (rw-rw-rw-). Netdata runs as a single dedicated user, so group/other access is unnecessary and adds latent risk if any local user can interfere with the lock file. flock(2) is purely advisory and does not enforce by mode, but the open() permission still controls who can create/access the file. Owner-only 0600 keeps the locking behavior intact for the netdata user while preventing unrelated local users from creating or opening the file. The function is currently unused (its caller in src/daemon/main.c is commented out), but it is exposed in the public header and may be revived for single-instance enforcement; tightening the default now avoids carrying permissive bits forward.
… -> 0666) Sonar c:S2612: create_listen_socket_unix() chmod'd the bound UNIX socket file to 0777. For UNIX domain socket files only the read/write permissions affect client connect() access -- the execute bit is unused. 0777 and 0666 are functionally identical for socket connect. Both callers (web API and statsd) intentionally allow arbitrary local clients to connect, so the broad rw permission is preserved with 0666. The execute bit was misleading and unnecessary; remove it and update the explanatory comment.
Sonar c:S1763: the `default:` case of the WndProc switch ended with `return DefWindowProc(...);` followed by `break;`. The break was unreachable. Remove the dead line.
…ine_sanitized Sonar c:S1751: comm_from_cmdline_sanitized() used `while (start)` to process the first occurrence of `comm` in the duplicated command-line buffer, but the body unconditionally returns and `start` is never updated -- the loop could never iterate twice. Replace with `if (start)` for accurate intent. No behavior change.
Sonar c:S876: ebpf_update_global_publish() computed `zombie = exit + (-release_task)` via in-place negation of the unsigned release_task counter. The unary minus on uint64_t is well-defined (modular arithmetic) but is a code smell and was unnecessary -- the intent was simple subtraction. Replace with a direct `(long)exit - (long)release_task`, matching the pattern used for `pvc->running` two lines above. Drop the in-place mutation; the release_task counter is not read elsewhere after this block. Numerical result is identical.
Sonar c:S876: chart_by_reason() iterated with `size_t i` and passed `-i` to stream_handshake_error_to_string(STREAM_HANDSHAKE). The unary minus on the unsigned counter wrapped to a huge unsigned value that then narrowed to int through implementation-defined conversion (C99 6.3.1.3). It happens to produce the correct -i on two's-complement platforms but is fragile and unportable. Change the loop variable to `int`. The loop bound STREAM_HANDSHAKE_NEGATIVE_MAX is 40, well within int range, and the b->rd[] array has more than enough entries for the same index. The negation is now well-defined signed arithmetic.
Sonar c:S3923: mcp_query_interrupt_callback() had a null guard (`if (!int_data || !int_data->mcpc) return false;`) followed by an unconditional `return false`. Both branches returned the same value and no field of int_data was ever dereferenced, so the guard was dead code. Remove the conditional, mark `data` unused with `(void)data;`, and keep the callback as an explicit "no interrupt" stub. The trailing comment about future client-disconnect/timeout detection is preserved.
Sonar go:S3923: sortRethinkRows() switched on sortColumn but the "durationMs" case and the default branch performed the same descending sort by DurationMs. RethinkDB only exposes durationMs as a server-side sort option (the only column with sortOpt: true in rethinkRunningColumns), so the switch was always going to take one of two identical paths. Drop the switch and sort directly. The unused parameter is renamed to `_` (Go idiom). Behaviour and call shape are unchanged.
mqtt_wss_client: SSL_set1_host() requires OpenSSL >= 1.1.0, but the amazonlinux:2 and centos7 build matrices ship OpenSSL 1.0.2 and the build broke with `undefined reference to 'SSL_set1_host'`. Switch to X509_VERIFY_PARAM_set1_host(SSL_get0_param(ssl), host, 0), which is available since OpenSSL 1.0.2 and provides the same hostname-verification behaviour. No semantic change, just portability. nd-mcp.py: the previous fix replaced `pass` with `raise` in the reconnect-delay CancelledError handler, but the empty `except CancelledError: raise` left the in-flight wait_task and retry_task as orphans and the retry_event in whatever state it was in when the cancellation arrived. Cancel both helper tasks and clear the event before re-raising, so the cancellation does not leave dangling state behind.
|
@cubic-dev-ai please review again |
@ktsaou I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 issue found across 25 files
Confidence score: 3/5
- There is a concrete user-impact risk in
src/aclk/mqtt_websockets/mqtt_wss_client.c: usingX509_VERIFY_PARAM_set1_hostalone may cause TLS certificate verification to fail for IP-literal connections even when the certificate has valid IP SAN entries. - Because this affects connection/authentication behavior for a real usage path (IP-based endpoints), the merge risk is moderate rather than minimal.
- This looks scoped to verification-target handling rather than a broad architectural problem, so it should be straightforward to fix once addressed.
- Pay close attention to
src/aclk/mqtt_websockets/mqtt_wss_client.c- hostname-only verification setup can reject valid IP-based certificates.
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/aclk/mqtt_websockets/mqtt_wss_client.c">
<violation number="1" location="src/aclk/mqtt_websockets/mqtt_wss_client.c:440">
P2: Handle IP literals when setting the verification target. X509_VERIFY_PARAM_set1_host only configures DNS hostname checks, so IP-based connections can fail certificate verification even with valid IP SANs.</violation>
</file>
Architecture diagram
sequenceDiagram
participant App as Application (ACLK/Claim/Plugin)
participant IPC as NetIPC Service Layer
participant TS as Transport Layer (UDS/SHM/Pipe)
participant FS as Filesystem/OS
participant Cloud as Cloud/MQTT Endpoint
Note over App,Cloud: Audit-Driven Security & Robustness Hardening
rect rgb(240, 248, 255)
Note over App,Cloud: 1. Secure Initialization & Permissions
App->>FS: Create lock/API/DB files
FS-->>App: CHANGED: Enforced 0600 mode (no world-read)
App->>FS: Create UNIX socket
FS-->>App: CHANGED: Enforced 0666 mode (dropped exec bit)
end
rect rgb(255, 245, 230)
Note over App,Cloud: 2. ACLK TLS Handshake Hardening
App->>Cloud: mqtt_wss_connect()
App->>App: NEW: X509_VERIFY_PARAM_set1_host()
App->>Cloud: SSL_connect()
alt Verification Failure
Cloud-->>App: Cert Mismatch / Self-Signed
opt MQTT_WSS_SSL_ALLOW_SELF_SIGNED is set
App->>App: CHANGED: Allow Hostname/IP mismatch errors
end
end
end
rect rgb(230, 255, 230)
Note over IPC,TS: 3. NetIPC Transport Recovery (Stale Path Checks)
IPC->>TS: Initialize Server
TS->>FS: lstat() / open(O_NOFOLLOW)
alt Path exists but is stale
TS->>TS: NEW: Verify inode/type matches expected
TS->>FS: unlink() stale path
else Path is live/socket active
TS-->>IPC: Return Error (Refuse overwrite)
end
end
rect rgb(245, 245, 245)
Note over App,TS: 4. Data Transfer & Buffer Safety
App->>IPC: Send Payload
IPC->>IPC: NEW: Guard against size_t overflow (32-bit safety)
IPC->>TS: Transport Send
TS->>TS: NEW: Validate total_msg fits packet/capacity
TS-->>IPC: Response Received
IPC->>IPC: NEW: Check response_len against negotiated capacity
IPC-->>App: Return Result
end
opt Failure Path
App->>App: NEW: Statsd-stress socket/memory cleanup on send failure
App->>App: NEW: nd-mcp.py cancels and drains in-flight tasks
end
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
X509_VERIFY_PARAM_set1_host() only matches against the certificate's dNSName SAN. When the agent connects to an IP literal (e.g. an on-prem deployment configured with 10.0.0.5 instead of a hostname), the verification fails even if the certificate has a valid iPAddress SAN because the IP literal is interpreted as a DNS name. Try X509_VERIFY_PARAM_set1_ip_asc() first -- it parses the input as an IP and matches against the iPAddress SAN, returning 0 when the input is not a valid IP. If that fails (the typical DNS hostname case), fall back to X509_VERIFY_PARAM_set1_host(). Both code paths are guarded by the existing MQTT_WSS_SSL_DONT_CHECK_CERTS opt-out, and the MQTT_WSS_SSL_ALLOW_SELF_SIGNED override already covers X509_V_ERR_IP_ADDRESS_MISMATCH for setups whose certs do not match.
|
@cubic-dev-ai please review again |
|
You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment |
@ktsaou I have started the AI code review. It will take a few minutes to complete. |
|
|
@stelfrag you can take over this. |


Summary
Audit-driven fixes for outstanding Coverity defects and SonarCloud findings,
landed as small focused commits.
curl_slist_free_allin thecurl_slist_appendfailure path. Coverity CID 503493 (DEADCODE):headersis initialised NULL and only assigned after the appendsucceeds; the cleanup
if(headers)is unreachable.(protocol/service/transport on POSIX and Windows).
convert_cgroup_to_systemd_service. Sonar c:S3519 (BLOCKER):while (len--)onsize_twraps toSIZE_MAXwhen the input has noseparator, causing
s[SIZE_MAX] = '\0'-- replaced both reverse scanswith
strrchr()+ explicit found-pointer checks.The remaining outstanding queue items (currently dominated by guard-
propagation and z-allocator-unawareness false positives) will be
addressed in subsequent commits on this branch.
Test plan
cgroups.pluginsmoke-tested on a host with systemd cgroupsSummary by cubic
Cleans up Coverity/Sonar findings and hardens
netipcacross POSIX/Windows. Enables strict TLS hostname verification in the MQTT WSS client with IP-literal support and opt-in overrides for self-signed/hostname mismatches.aclk/mqtt_wss: enable TLS peer identity checks; tryX509_VERIFY_PARAM_set1_ip_ascfor IP literals and fall back toX509_VERIFY_PARAM_set1_host; acceptX509_V_ERR_HOSTNAME_MISMATCH/X509_V_ERR_IP_ADDRESS_MISMATCHwhenMQTT_WSS_SSL_ALLOW_SELF_SIGNEDis set; respectMQTT_WSS_SSL_DONT_CHECK_CERTS.claim: removed unreachablecurl_slist_free_allon append failure (Coverity CID 503493).claim/ui(Windows): removed unreachablebreakinWndProcdefault case.cgroups.plugin: fixed unsigned-underflow/OOB inconvert_cgroup_to_systemd_serviceusingstrrchr()with explicit checks.apps.plugin: replaced single-iterationwhile (start)withif (start)incomm_from_cmdline_sanitized.ebpf.plugin: computezombieasexit - release_task(drop unsigned unary minus and in-place mutation).pulse: useintloop variable when negating toSTREAM_HANDSHAKEenum.netipcprotocol/service: added safe header+payload length helpers; guarded 32-bitsize_toverflows; validated response sizes and buffer bounds; added null checks in server init; safer client/server buffer sizing.netipcPOSIX SHM/UDS: safer stale-path recovery—useopenwithO_NOFOLLOW/lstat, verify inode/type beforeunlink, unlink only on proven-stale sockets (ECONNREFUSED); validated SHM region size math.netipcWindows SHM/Named Pipes: mirrored overflow/capacity guards; validated region sizing; computed message lengths with overflow checks before send/receive.exporting: fixed UNIT_TESTING early returns to match function signatures—aws_kinesis_connector_workerandpubsub_connector_workernow returnNULL;exporting_mainuses a barereturn.tests:statsd-stressfrees per-packet strings/arrays and closes the socket onsendto()failure; removed dead post-loop cleanup.mcp: dropped redundant null guard inmcp_query_interrupt_callback; marked parameter unused.go.d/rethinkdb: removed degenerate switch insortRethinkRows; always sort byDurationMs.api: create management API key file with mode 0600.sqlite: create recovery marker files with mode 0600.os: create lock files with mode 0600.socket: drop exec bit on UNIX socket files; chmod0666instead of0777.nd-mcp.py: re-raiseasyncio.CancelledErrorduring reconnect-delay; cancel in-flight wait/retry tasks and clear the event before re-raising; afterasyncio.wait, cancel and drain pending tasks viaasyncio.gather(return_exceptions=True), re-raising non-cancellation exceptions.Written for commit 7623386. Summary will update on new commits. Review in cubic