Skip to content

Commit cb0091b

Browse files
committed
Harden netipc string copies
1 parent 8b20d7a commit cb0091b

7 files changed

Lines changed: 124 additions & 52 deletions

File tree

.agents/sow/current/SOW-0015-20260605-codacy-scope-and-maintainability.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,30 @@ Validation completed for this follow-up:
593593
- Win11 temp-copy Rust validation: `cargo test --manifest-path src/crates/netipc/Cargo.toml service::raw -- --test-threads=1`: 22 Windows raw-service tests passed.
594594
- Win11 temp-copy Go validation: `cd src/go && CGO_ENABLED=0 go test -count=1 ./pkg/netipc/service/raw ./pkg/netipc/transport/windows`: passed.
595595

596+
### 2026-06-07 - PR 22649 SonarCloud Hotspot Follow-up
597+
598+
Pre-push Netdata PR #22649 sync found nine SonarCloud security hotspots on the old PR head:
599+
600+
- `src/libnetdata/netipc/src/transport/posix/netipc_uds_lifecycle.c`: four `strncpy` hotspot reports around socket path copies.
601+
- `src/libnetdata/netipc/src/service/netipc_service_common.c`: one `strlen` hotspot in service context field copying.
602+
- `src/libnetdata/netipc/src/service/netipc_service_posix_server.c`: two `strlen` hotspots in server config field copying.
603+
- `src/libnetdata/netipc/src/service/netipc_service_win_server.c`: two `strlen` hotspots in Windows server config field copying.
604+
605+
Implemented SDK follow-up:
606+
607+
- Replaced POSIX UDS `strncpy` socket/path copies with a checked NUL-terminated bounded copy helper and a `sockaddr_un` fill helper.
608+
- Replaced repeated POSIX and Windows service server `strlen`/`memcpy` config-field copies with `nipc_service_common_copy_cstr_field()`.
609+
- Changed the common service copy helper to avoid open-ended `strlen` while preserving previous truncating field-copy behavior.
610+
- Same-pattern search found two additional `strncpy` copies in POSIX SHM context path storage; those were changed to checked bounded copies in the same increment.
611+
612+
Validation completed for this hotspot follow-up:
613+
614+
- `git diff --check`: passed.
615+
- `rg "strncpy\\(|strlen\\(run_dir\\)|strlen\\(service_name\\)|size_t len = strlen" src/libnetdata/netipc/src src/libnetdata/netipc/include`: only the unrelated Windows named-pipe hash input remains.
616+
- `cmake --build build`: passed.
617+
- `/usr/bin/ctest --test-dir build --output-on-failure`: 46/46 tests passed.
618+
- Win11 temp-copy focused C validation: MSYS CMake build of `test_win_service`, `test_win_service_extra`, and `test_win_service_payload_limits` passed; CTest for those three tests passed.
619+
596620
## Validation
597621

598622
Acceptance criteria evidence:
@@ -606,6 +630,7 @@ Acceptance criteria evidence:
606630
- Vendor script now copies the full vendored C include/source subtrees so split C files are not missed.
607631
- Netdata PR #22649 review and SonarCloud findings were verified against SDK source and addressed in the SDK before re-vendoring.
608632
- Plugin-ipc Go modules now match Netdata's Go version, `go 1.26.0`.
633+
- Netdata PR #22649 SonarCloud security hotspots for C string/path copying were verified and addressed in the SDK before re-vendoring.
609634

610635
Tests or equivalent validation:
611636

@@ -617,6 +642,7 @@ Tests or equivalent validation:
617642
- `cd src/go && go test -count=1 ./...`: passed after restoring the required protocol-level lookup dispatch guard.
618643
- Win11 temp-copy Rust raw-service validation: 22 tests passed.
619644
- Win11 temp-copy Go service/raw and transport/windows validation: passed.
645+
- Win11 temp-copy C service validation: `test_win_service`, `test_win_service_extra`, and `test_win_service_payload_limits` built and passed under MSYS.
620646
- `git diff --check`: passed.
621647
- `bash -n tests/run-windows-bench.sh tests/test_windows_bench_stability_policy.sh vendor-to-netdata.sh`: passed.
622648
- `codacy-analysis analyze --files ... --output-format json`: 0 issues; known local Revive adapter invocation error remains.
@@ -632,6 +658,7 @@ Reviewer findings:
632658
- GitHub AI findings for `netipc_uds_send.c` were manually verified and addressed.
633659
- GitHub review-thread findings from Netdata PR #22649 were manually verified and addressed where they were real.
634660
- SonarCloud PR findings for Netdata PR #22649 were queried and addressed in the SDK source before re-vendoring.
661+
- SonarCloud PR security hotspots for C `strncpy` and open-ended config field length/copy patterns were queried and addressed in the SDK source before re-vendoring.
635662
- No external AI reviewer was used for this increment.
636663

637664
Same-failure scan:
@@ -641,6 +668,7 @@ Same-failure scan:
641668
- Same benchmark batch sizing nondeterminism pattern was found and fixed in POSIX and Windows Go benchmark drivers.
642669
- Same dead Go service-level lookup dispatch guard was found and removed in apps lookup and cgroups lookup.
643670
- Similar Go protocol-level lookup dispatch guards were tested and kept because regression coverage proves they still guard an overflow state.
671+
- Same C `strncpy` path-copy pattern was found beyond the reported UDS lifecycle file in POSIX SHM context path storage and fixed in the same increment.
644672

645673
Sensitive data gate:
646674

src/libnetdata/netipc/src/service/netipc_service_common.c

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,20 @@ uint32_t nipc_service_common_typed_response_batch_items(uint32_t max_request_bat
6363
return max_request_batch_items;
6464
}
6565

66-
static void copy_cstr_field(char *dst, size_t dst_size, const char *src)
66+
void nipc_service_common_copy_cstr_field(char *dst, size_t dst_size, const char *src)
6767
{
68-
if (!src || dst_size == 0)
68+
if (!dst || dst_size == 0)
6969
return;
7070

71-
size_t len = strlen(src);
72-
if (len >= dst_size)
73-
len = dst_size - 1;
71+
dst[0] = '\0';
72+
if (!src)
73+
return;
74+
75+
size_t len = 0;
76+
size_t max_copy = dst_size - 1;
77+
while (len < max_copy && src[len] != '\0')
78+
len++;
79+
7480
memcpy(dst, src, len);
7581
dst[len] = '\0';
7682
}
@@ -83,8 +89,8 @@ void nipc_service_common_client_init(nipc_client_ctx_t *ctx,
8389
ctx->state = NIPC_CLIENT_DISCONNECTED;
8490
ctx->session_valid = false;
8591
ctx->shm = NULL;
86-
copy_cstr_field(ctx->run_dir, sizeof(ctx->run_dir), run_dir);
87-
copy_cstr_field(ctx->service_name, sizeof(ctx->service_name), service_name);
92+
nipc_service_common_copy_cstr_field(ctx->run_dir, sizeof(ctx->run_dir), run_dir);
93+
nipc_service_common_copy_cstr_field(ctx->service_name, sizeof(ctx->service_name), service_name);
8894
}
8995

9096
void nipc_service_common_client_status(const nipc_client_ctx_t *ctx,

src/libnetdata/netipc/src/service/netipc_service_common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ bool nipc_service_common_mul_would_overflow(size_t count, size_t size);
3030
uint32_t nipc_service_common_request_payload_default(void);
3131
uint32_t nipc_service_common_response_payload_default(void);
3232
uint32_t nipc_service_common_typed_response_batch_items(uint32_t max_request_batch_items);
33+
void nipc_service_common_copy_cstr_field(char *dst, size_t dst_size, const char *src);
3334

3435
void nipc_service_common_client_init(nipc_client_ctx_t *ctx,
3536
const char *run_dir,

src/libnetdata/netipc/src/service/netipc_service_posix_server.c

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,11 @@ nipc_error_t nipc_service_platform_server_init_raw(
117117
if (worker_count < 1)
118118
worker_count = 1;
119119

120-
/* Store config */
121-
{
122-
size_t len = strlen(run_dir);
123-
if (len >= sizeof(server->run_dir))
124-
len = sizeof(server->run_dir) - 1;
125-
memcpy(server->run_dir, run_dir, len);
126-
server->run_dir[len] = '\0';
127-
}
128-
{
129-
size_t len = strlen(service_name);
130-
if (len >= sizeof(server->service_name))
131-
len = sizeof(server->service_name) - 1;
132-
memcpy(server->service_name, service_name, len);
133-
server->service_name[len] = '\0';
134-
}
120+
nipc_service_common_copy_cstr_field(server->run_dir, sizeof(server->run_dir),
121+
run_dir);
122+
nipc_service_common_copy_cstr_field(server->service_name,
123+
sizeof(server->service_name),
124+
service_name);
135125

136126
server->handler = handler;
137127
server->handler_user = user;

src/libnetdata/netipc/src/service/netipc_service_win_server.c

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -196,21 +196,11 @@ nipc_error_t nipc_service_platform_server_init_raw(
196196
if (worker_count < 1)
197197
worker_count = 1;
198198

199-
/* Store config */
200-
{
201-
size_t len = strlen(run_dir);
202-
if (len >= sizeof(server->run_dir))
203-
len = sizeof(server->run_dir) - 1;
204-
memcpy(server->run_dir, run_dir, len);
205-
server->run_dir[len] = '\0';
206-
}
207-
{
208-
size_t len = strlen(service_name);
209-
if (len >= sizeof(server->service_name))
210-
len = sizeof(server->service_name) - 1;
211-
memcpy(server->service_name, service_name, len);
212-
server->service_name[len] = '\0';
213-
}
199+
nipc_service_common_copy_cstr_field(server->run_dir, sizeof(server->run_dir),
200+
run_dir);
201+
nipc_service_common_copy_cstr_field(server->service_name,
202+
sizeof(server->service_name),
203+
service_name);
214204

215205
server->handler = handler;
216206
server->handler_user = user;

src/libnetdata/netipc/src/transport/posix/netipc_shm.c

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ static inline uint32_t align64(uint32_t v)
3535
return (v + (NIPC_SHM_REGION_ALIGNMENT - 1)) & ~(uint32_t)(NIPC_SHM_REGION_ALIGNMENT - 1);
3636
}
3737

38+
static int copy_cstr_checked(char *dst, size_t dst_size, const char *src)
39+
{
40+
if (!dst || !src || dst_size == 0)
41+
return -1;
42+
43+
size_t len = 0;
44+
while (len < dst_size && src[len] != '\0')
45+
len++;
46+
if (len == dst_size)
47+
return -1;
48+
49+
memcpy(dst, src, len + 1);
50+
return 0;
51+
}
52+
3853
/* Validate service_name: only [a-zA-Z0-9._-], non-empty, not "." or "..". */
3954
static int validate_service_name(const char *name)
4055
{
@@ -390,8 +405,15 @@ nipc_shm_error_t nipc_shm_server_create(const char *run_dir,
390405
out->local_resp_seq = 0;
391406
out->spin_tries = NIPC_SHM_DEFAULT_SPIN;
392407
out->owner_generation = hdr->owner_generation;
393-
strncpy(out->path, path, sizeof(out->path) - 1);
394-
out->path[sizeof(out->path) - 1] = '\0';
408+
if (copy_cstr_checked(out->path, sizeof(out->path), path) != 0) {
409+
munmap(map, region_size);
410+
close(fd);
411+
unlinkat(dir_fd, shm_name, 0);
412+
close(dir_fd);
413+
memset(out, 0, sizeof(*out));
414+
out->fd = -1;
415+
return NIPC_SHM_ERR_PATH_TOO_LONG;
416+
}
395417

396418
close(dir_fd);
397419
return NIPC_SHM_OK;
@@ -570,8 +592,13 @@ nipc_shm_error_t nipc_shm_client_attach(const char *run_dir,
570592
out->local_resp_seq = cur_resp_seq;
571593
out->spin_tries = NIPC_SHM_DEFAULT_SPIN;
572594
out->owner_generation = hdr->owner_generation;
573-
strncpy(out->path, path, sizeof(out->path) - 1);
574-
out->path[sizeof(out->path) - 1] = '\0';
595+
if (copy_cstr_checked(out->path, sizeof(out->path), path) != 0) {
596+
munmap(map, file_size);
597+
close(fd);
598+
memset(out, 0, sizeof(*out));
599+
out->fd = -1;
600+
return NIPC_SHM_ERR_PATH_TOO_LONG;
601+
}
575602

576603
return NIPC_SHM_OK;
577604
}

src/libnetdata/netipc/src/transport/posix/netipc_uds_lifecycle.c

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,28 @@
1212
#include <sys/stat.h>
1313
#include <sys/un.h>
1414

15+
static int copy_cstr_checked(char *dst, size_t dst_size, const char *src)
16+
{
17+
if (!dst || !src || dst_size == 0)
18+
return -1;
19+
20+
size_t len = 0;
21+
while (len < dst_size && src[len] != '\0')
22+
len++;
23+
if (len == dst_size)
24+
return -1;
25+
26+
memcpy(dst, src, len + 1);
27+
return 0;
28+
}
29+
30+
static int fill_sockaddr_path(struct sockaddr_un *addr, const char *path)
31+
{
32+
memset(addr, 0, sizeof(*addr));
33+
addr->sun_family = AF_UNIX;
34+
return copy_cstr_checked(addr->sun_path, sizeof(addr->sun_path), path);
35+
}
36+
1537
static int validate_service_name(const char *name)
1638
{
1739
if (!name || name[0] == '\0')
@@ -118,9 +140,10 @@ int nipc_uds_check_and_recover_stale(const char *run_dir,
118140
return -1;
119141

120142
struct sockaddr_un addr;
121-
memset(&addr, 0, sizeof(addr));
122-
addr.sun_family = AF_UNIX;
123-
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
143+
if (fill_sockaddr_path(&addr, path) != 0) {
144+
close(probe);
145+
return -1;
146+
}
124147

125148
int ret;
126149
if (connect(probe, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
@@ -176,9 +199,10 @@ nipc_uds_error_t nipc_uds_listen(const char *run_dir,
176199
return NIPC_UDS_ERR_SOCKET;
177200

178201
struct sockaddr_un addr;
179-
memset(&addr, 0, sizeof(addr));
180-
addr.sun_family = AF_UNIX;
181-
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
202+
if (fill_sockaddr_path(&addr, path) != 0) {
203+
close(fd);
204+
return NIPC_UDS_ERR_PATH_TOO_LONG;
205+
}
182206

183207
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
184208
close(fd);
@@ -194,8 +218,13 @@ nipc_uds_error_t nipc_uds_listen(const char *run_dir,
194218

195219
out->fd = fd;
196220
out->config = *config;
197-
strncpy(out->path, path, sizeof(out->path) - 1);
198-
out->path[sizeof(out->path) - 1] = '\0';
221+
if (copy_cstr_checked(out->path, sizeof(out->path), path) != 0) {
222+
close(fd);
223+
unlink(path);
224+
memset(out, 0, sizeof(*out));
225+
out->fd = -1;
226+
return NIPC_UDS_ERR_PATH_TOO_LONG;
227+
}
199228

200229
return NIPC_UDS_OK;
201230
}
@@ -243,9 +272,10 @@ nipc_uds_error_t nipc_uds_connect(const char *run_dir,
243272
return NIPC_UDS_ERR_SOCKET;
244273

245274
struct sockaddr_un addr;
246-
memset(&addr, 0, sizeof(addr));
247-
addr.sun_family = AF_UNIX;
248-
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
275+
if (fill_sockaddr_path(&addr, path) != 0) {
276+
close(fd);
277+
return NIPC_UDS_ERR_PATH_TOO_LONG;
278+
}
249279

250280
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
251281
close(fd);

0 commit comments

Comments
 (0)