Skip to content

Commit 7c590c3

Browse files
committed
Fix expanded C CodeQL findings
1 parent 97557fd commit 7c590c3

19 files changed

Lines changed: 281 additions & 135 deletions

.agents/sow/current/SOW-0014-20260603-maintainability-hotspots.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,55 @@ Raw cache, Go typed-facade, apps lookup builder, cgroups lookup builder, apps lo
920920
- `bash .agents/sow/audit.sh` passed.
921921
- `codacy-analysis analyze --output-format json` passed with 0 issues and 0 errors across Checkov, Opengrep/Semgrep, Trivy, cppcheck, ShellCheck, and Spectral.
922922

923+
### 2026-06-04 Expanded CodeQL Findings
924+
925+
- After commit `97557fd`, GitHub CodeQL reported 56 open code-scanning alerts, all from category `/language:c-cpp-posix`.
926+
- Evidence from GitHub code scanning:
927+
- `cpp/path-injection`: 12 high alerts across POSIX SHM production transport, C interop fixtures, fuzz helper, and POSIX benchmark helper.
928+
- `cpp/world-writable-file-creation`: 2 high alerts in C test/interop helpers.
929+
- `cpp/wrong-type-format-argument`: 1 high alert in C ping-pong test output.
930+
- `cpp/stack-address-escape`: 4 warning alerts across C POSIX service, payload-limit test, and benchmark helper.
931+
- `cpp/unused-local-variable`: 31 note alerts in C tests and benchmark helper.
932+
- `cpp/unused-static-function`: 5 note alerts in C protocol/SHM tests.
933+
- `cpp/long-switch`: 1 note alert in C UDS fault-response test.
934+
- Root-cause model:
935+
- The stronger C/C++ CodeQL build now compiles test, interop, fuzz, cache, stress, hardening, and benchmark C targets that were not extracted by the earlier default setup.
936+
- Most alerts are newly visible pre-existing code patterns, not regressions introduced by the Windows CI workflow itself.
937+
- Some alerts are real hygiene issues in test and benchmark code; at least two path findings and one stack-address warning touch production POSIX C code and need source review rather than dismissal.
938+
- Decision:
939+
- Do not weaken CodeQL coverage or disable the rules by default.
940+
- First fix source patterns that are real or cheaply made explicit.
941+
- Use narrow suppression or workflow configuration only for remaining findings proven to be intentional test scaffolding after source review.
942+
- Risk:
943+
- Production POSIX SHM path handling changes can affect SHM lifecycle, stale-file recovery, and interop tests.
944+
- Stack-lifetime fixes around server/session code can affect thread and signal lifetime if handled mechanically.
945+
- Removing stale test locals and wiring previously unused static tests is low risk but still needs CTest validation.
946+
- Validation plan:
947+
- Build C targets locally with CMake.
948+
- Run focused C protocol, POSIX UDS/SHM/service, fuzz, and benchmark target validation.
949+
- Run action/static checks available locally.
950+
- Push and verify GitHub CodeQL alerts close or reduce to only documented intentional findings.
951+
- Implemented:
952+
- Removed stale typed-call request/response locals from C service, chaos, and benchmark tests.
953+
- Wired dormant C protocol coverage tests into `test_protocol` and relaxed one overly-specific synthetic-overflow assertion to require rejection rather than a single error code.
954+
- Removed an unused SHM test helper.
955+
- Fixed portable `uint32_t` formatting in the C ping-pong test.
956+
- Changed the C payload-limit test overflow fixture to use static storage instead of sharing a stack buffer with server handler state.
957+
- Changed POSIX benchmark timer threads to receive heap-owned duration arguments instead of stack addresses.
958+
- Changed POSIX SHM stale recovery and attach/create paths to open session files with `openat()` relative to a validated directory file descriptor and to unlink with `unlinkat()` where the directory identity matters.
959+
- Added a C interop/benchmark run-directory helper requiring existing absolute non-symlink directories owned by the current user and not group/world writable.
960+
- Removed C interop/benchmark helper-side `mkdir(run_dir)` calls; repo scripts already create the temporary run directories.
961+
- Changed the C codec interop fixture writer to create files with explicit `0600` mode.
962+
- Changed the standalone C fuzz harness to read bytes from stdin only and updated the extended fuzz script accordingly.
963+
- Changed the UDS stale-recovery regular-file test fixture to use explicit `0600` file creation.
964+
- Local validation:
965+
- `cmake --build build --target netipc_shm netipc_service test_protocol test_uds test_shm test_service test_service_payload_limits test_chaos test_ping_pong fuzz_protocol interop_codec_c interop_uds_c interop_shm_c interop_service_c interop_cache_c bench_posix_c` passed.
966+
- `/usr/bin/ctest --test-dir build --output-on-failure -R '^(test_protocol|test_uds|test_shm|test_service|test_service_payload_limits|test_chaos|test_ping_pong|fuzz_protocol_30s|interop_codec|test_uds_interop|test_shm_interop|test_service_interop|test_cache_interop)$'` passed: 13/13 focused tests.
967+
- `codacy-analysis analyze --output-format json` passed with 0 issues and 0 errors across Checkov, Opengrep/Semgrep, Trivy, cppcheck, ShellCheck, and Spectral.
968+
- `git diff --check` passed.
969+
- `bash .agents/sow/audit.sh` passed.
970+
- Sensitive-data scan across the touched durable artifacts returned no matches.
971+
923972
## Lessons Extracted
924973

925974
Pending.

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,8 @@ endif()
968968
if(NOT NETIPC_WINDOWS_RUNTIME AND NOT APPLE)
969969
# C benchmark binary
970970
add_executable(bench_posix_c bench/drivers/c/bench_posix.c)
971+
target_include_directories(bench_posix_c PRIVATE
972+
"${CMAKE_SOURCE_DIR}/tests/fixtures/c")
971973
target_link_libraries(bench_posix_c PRIVATE
972974
netipc_service netipc_shm netipc_uds netipc_protocol Threads::Threads m
973975
)

bench/drivers/c/bench_posix.c

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "netipc/netipc_protocol.h"
2828
#include "netipc/netipc_uds.h"
2929
#include "netipc/netipc_shm.h"
30+
#include "interop_path.h"
3031

3132
#include <errno.h>
3233
#include <math.h>
@@ -313,7 +314,9 @@ static void sighandler(int sig)
313314
/* Timer thread: stops server after duration_sec */
314315
static void *timer_thread(void *arg)
315316
{
316-
int duration_sec = *(int *)arg;
317+
int *duration_arg = (int *)arg;
318+
int duration_sec = *duration_arg;
319+
free(duration_arg);
317320
sleep(duration_sec + 3);
318321
if (g_server)
319322
nipc_server_stop(g_server);
@@ -368,11 +371,20 @@ static int run_server(const char *run_dir, const char *service,
368371
pthread_t timer_tid = 0;
369372
int timer_failed = 0;
370373
if (duration_sec > 0) {
371-
int rc = pthread_create(&timer_tid, NULL, timer_thread, &duration_sec);
372-
if (rc != 0) {
373-
fprintf(stderr, "timer thread create failed: %s\n", strerror(rc));
374+
int *duration_arg = malloc(sizeof(*duration_arg));
375+
if (!duration_arg) {
376+
fprintf(stderr, "timer duration allocation failed\n");
374377
timer_failed = 1;
375378
nipc_server_stop(&server);
379+
} else {
380+
*duration_arg = duration_sec;
381+
int rc = pthread_create(&timer_tid, NULL, timer_thread, duration_arg);
382+
if (rc != 0) {
383+
free(duration_arg);
384+
fprintf(stderr, "timer thread create failed: %s\n", strerror(rc));
385+
timer_failed = 1;
386+
nipc_server_stop(&server);
387+
}
376388
}
377389
}
378390

@@ -443,11 +455,20 @@ static int run_server_batch(const char *run_dir, const char *service,
443455
pthread_t timer_tid = 0;
444456
int timer_failed = 0;
445457
if (duration_sec > 0) {
446-
int rc = pthread_create(&timer_tid, NULL, timer_thread, &duration_sec);
447-
if (rc != 0) {
448-
fprintf(stderr, "batch timer thread create failed: %s\n", strerror(rc));
458+
int *duration_arg = malloc(sizeof(*duration_arg));
459+
if (!duration_arg) {
460+
fprintf(stderr, "batch timer duration allocation failed\n");
449461
timer_failed = 1;
450462
nipc_server_stop(&server);
463+
} else {
464+
*duration_arg = duration_sec;
465+
int rc = pthread_create(&timer_tid, NULL, timer_thread, duration_arg);
466+
if (rc != 0) {
467+
free(duration_arg);
468+
fprintf(stderr, "batch timer thread create failed: %s\n", strerror(rc));
469+
timer_failed = 1;
470+
nipc_server_stop(&server);
471+
}
451472
}
452473
}
453474

@@ -924,9 +945,6 @@ static int run_snapshot_client(const char *run_dir, const char *service,
924945

925946
uint64_t requests = 0;
926947
uint64_t errors = 0;
927-
uint8_t req_buf[64];
928-
uint8_t resp_buf[RESPONSE_BUF_SIZE];
929-
930948
uint64_t cpu_start = cpu_ns();
931949
uint64_t wall_start = now_ns();
932950
uint64_t wall_end = wall_start + (uint64_t)duration_sec * 1000000000ull;
@@ -1362,12 +1380,12 @@ int main(int argc, char **argv)
13621380
return 1;
13631381
}
13641382

1365-
const char *run_dir = argv[2];
1383+
char run_dir[PATH_MAX];
1384+
if (nipc_test_resolve_run_dir(argv[2], run_dir, sizeof(run_dir)) != 0)
1385+
return 1;
13661386
const char *service = argv[3];
13671387
int duration = (argc >= 5) ? atoi(argv[4]) : DEFAULT_DURATION;
13681388

1369-
mkdir(run_dir, 0700);
1370-
13711389
uint32_t profiles;
13721390
uint16_t expected_method_code;
13731391
nipc_server_handler_fn handler;
@@ -1403,7 +1421,9 @@ int main(int argc, char **argv)
14031421
return 1;
14041422
}
14051423

1406-
const char *run_dir = argv[2];
1424+
char run_dir[PATH_MAX];
1425+
if (nipc_test_resolve_run_dir(argv[2], run_dir, sizeof(run_dir)) != 0)
1426+
return 1;
14071427
const char *service = argv[3];
14081428
int duration = atoi(argv[4]);
14091429
uint64_t target_rps = (uint64_t)strtoull(argv[5], NULL, 10);
@@ -1434,7 +1454,9 @@ int main(int argc, char **argv)
14341454
return 1;
14351455
}
14361456

1437-
const char *run_dir = argv[2];
1457+
char run_dir[PATH_MAX];
1458+
if (nipc_test_resolve_run_dir(argv[2], run_dir, sizeof(run_dir)) != 0)
1459+
return 1;
14381460
const char *service = argv[3];
14391461
int duration = atoi(argv[4]);
14401462
uint64_t target_rps = (uint64_t)strtoull(argv[5], NULL, 10);
@@ -1464,12 +1486,12 @@ int main(int argc, char **argv)
14641486
return 1;
14651487
}
14661488

1467-
const char *run_dir = argv[2];
1489+
char run_dir[PATH_MAX];
1490+
if (nipc_test_resolve_run_dir(argv[2], run_dir, sizeof(run_dir)) != 0)
1491+
return 1;
14681492
const char *service = argv[3];
14691493
int duration = (argc >= 5) ? atoi(argv[4]) : DEFAULT_DURATION;
14701494

1471-
mkdir(run_dir, 0700);
1472-
14731495
uint32_t profiles;
14741496
if (strcmp(cmd, "uds-batch-ping-pong-server") == 0)
14751497
profiles = BENCH_PROFILE_UDS;
@@ -1490,7 +1512,9 @@ int main(int argc, char **argv)
14901512
return 1;
14911513
}
14921514

1493-
const char *run_dir = argv[2];
1515+
char run_dir[PATH_MAX];
1516+
if (nipc_test_resolve_run_dir(argv[2], run_dir, sizeof(run_dir)) != 0)
1517+
return 1;
14941518
const char *service = argv[3];
14951519
int duration = atoi(argv[4]);
14961520
uint64_t target_rps = (uint64_t)strtoull(argv[5], NULL, 10);
@@ -1518,7 +1542,9 @@ int main(int argc, char **argv)
15181542
return 1;
15191543
}
15201544

1521-
const char *run_dir = argv[2];
1545+
char run_dir[PATH_MAX];
1546+
if (nipc_test_resolve_run_dir(argv[2], run_dir, sizeof(run_dir)) != 0)
1547+
return 1;
15221548
const char *service = argv[3];
15231549
int duration = atoi(argv[4]);
15241550
uint64_t target_rps = (uint64_t)strtoull(argv[5], NULL, 10);
@@ -1685,7 +1711,9 @@ int main(int argc, char **argv)
16851711
return 1;
16861712
}
16871713

1688-
const char *run_dir = argv[2];
1714+
char run_dir[PATH_MAX];
1715+
if (nipc_test_resolve_run_dir(argv[2], run_dir, sizeof(run_dir)) != 0)
1716+
return 1;
16891717
const char *service = argv[3];
16901718
int duration = atoi(argv[4]);
16911719
uint64_t target_rps = (uint64_t)strtoull(argv[5], NULL, 10);

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

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ static int build_shm_path(char *dst, size_t dst_len,
8686
return 0;
8787
}
8888

89-
static bool run_dir_allows_stale_unlink(const char *run_dir)
89+
static bool dir_fd_allows_stale_unlink(int dir_fd)
9090
{
9191
struct stat st;
92-
if (stat(run_dir, &st) != 0)
92+
if (dir_fd < 0 || fstat(dir_fd, &st) != 0)
9393
return false;
9494
if (!S_ISDIR(st.st_mode))
9595
return false;
@@ -189,13 +189,13 @@ static int unlink_same_file(int dir_fd, const char *name, const struct stat *exp
189189
return -1;
190190
}
191191

192-
static int check_shm_stale(const char *path, int dir_fd, const char *name,
192+
static int check_shm_stale(int dir_fd, const char *name,
193193
bool allow_stale_unlink)
194194
{
195195
#ifdef O_NOFOLLOW
196-
int fd = open(path, O_RDONLY | O_NOFOLLOW);
196+
int fd = openat(dir_fd, name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
197197
#else
198-
int fd = open(path, O_RDONLY);
198+
int fd = openat(dir_fd, name, O_RDONLY | O_CLOEXEC);
199199
#endif
200200
if (fd < 0) {
201201
if (errno == ENOENT)
@@ -282,53 +282,62 @@ nipc_shm_error_t nipc_shm_server_create(const char *run_dir,
282282
if (name_rc < 0)
283283
return NIPC_SHM_ERR_PATH_TOO_LONG;
284284

285+
int dir_fd = open(run_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
286+
if (dir_fd < 0)
287+
return NIPC_SHM_ERR_OPEN;
288+
285289
/* Round capacities up to alignment. */
286290
req_capacity = align64(req_capacity);
287291
resp_capacity = align64(resp_capacity);
288292

289293
uint32_t req_off = align64(NIPC_SHM_HEADER_LEN);
290294
/* Guard against uint32 overflow before computing resp_off */
291-
if (req_capacity > UINT32_MAX - req_off)
295+
if (req_capacity > UINT32_MAX - req_off) {
296+
close(dir_fd);
292297
return NIPC_SHM_ERR_BAD_PARAM;
298+
}
293299
uint32_t resp_off = align64(req_off + req_capacity);
294-
if (resp_capacity > UINT32_MAX - resp_off)
300+
if (resp_capacity > UINT32_MAX - resp_off) {
301+
close(dir_fd);
295302
return NIPC_SHM_ERR_BAD_PARAM;
303+
}
296304
size_t region_size = (size_t)resp_off + resp_capacity;
297305

298306
/* Try O_EXCL create first (fast path, no stale check needed). */
299-
int fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
307+
int fd = openat(dir_fd, shm_name,
308+
O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
300309

301310
/* If O_EXCL failed because file exists, do stale recovery and retry. */
302311
if (fd < 0 && errno == EEXIST) {
303-
bool allow_stale_unlink = run_dir_allows_stale_unlink(run_dir);
304-
int dir_fd = allow_stale_unlink
305-
? open(run_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC)
306-
: -1;
307-
if (dir_fd < 0)
308-
allow_stale_unlink = false;
309-
310-
int stale = check_shm_stale(path, dir_fd, shm_name, allow_stale_unlink);
311-
if (dir_fd >= 0)
312+
bool allow_stale_unlink = dir_fd_allows_stale_unlink(dir_fd);
313+
314+
int stale = check_shm_stale(dir_fd, shm_name, allow_stale_unlink);
315+
if (stale == 1) {
312316
close(dir_fd);
313-
if (stale == 1)
314317
return NIPC_SHM_ERR_ADDR_IN_USE;
318+
}
315319
/* Stale file was unlinked, retry create */
316-
fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
320+
fd = openat(dir_fd, shm_name,
321+
O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
317322
}
318-
if (fd < 0)
323+
if (fd < 0) {
324+
close(dir_fd);
319325
return NIPC_SHM_ERR_OPEN;
326+
}
320327

321328
if (ftruncate(fd, (off_t)region_size) < 0) {
322329
close(fd);
323-
unlink(path);
330+
unlinkat(dir_fd, shm_name, 0);
331+
close(dir_fd);
324332
return NIPC_SHM_ERR_TRUNCATE;
325333
}
326334

327335
void *map = mmap(NULL, region_size, PROT_READ | PROT_WRITE,
328336
MAP_SHARED, fd, 0);
329337
if (map == MAP_FAILED) {
330338
close(fd);
331-
unlink(path);
339+
unlinkat(dir_fd, shm_name, 0);
340+
close(dir_fd);
332341
return NIPC_SHM_ERR_MMAP;
333342
}
334343

@@ -372,6 +381,7 @@ nipc_shm_error_t nipc_shm_server_create(const char *run_dir,
372381
strncpy(out->path, path, sizeof(out->path) - 1);
373382
out->path[sizeof(out->path) - 1] = '\0';
374383

384+
close(dir_fd);
375385
return NIPC_SHM_OK;
376386
}
377387

@@ -425,8 +435,20 @@ nipc_shm_error_t nipc_shm_client_attach(const char *run_dir,
425435
if (path_rc < 0)
426436
return NIPC_SHM_ERR_PATH_TOO_LONG;
427437

438+
char shm_name[256];
439+
int name_rc = build_shm_name(shm_name, sizeof(shm_name), service_name,
440+
session_id);
441+
if (name_rc == -2)
442+
return NIPC_SHM_ERR_BAD_PARAM;
443+
if (name_rc < 0)
444+
return NIPC_SHM_ERR_PATH_TOO_LONG;
445+
428446
/* Open the file. */
429-
int fd = open(path, O_RDWR);
447+
int dir_fd = open(run_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
448+
if (dir_fd < 0)
449+
return NIPC_SHM_ERR_OPEN;
450+
int fd = openat(dir_fd, shm_name, O_RDWR | O_CLOEXEC);
451+
close(dir_fd);
430452
if (fd < 0)
431453
return NIPC_SHM_ERR_OPEN;
432454

@@ -830,7 +852,7 @@ void nipc_shm_cleanup_stale(const char *run_dir, const char *service_name)
830852
return;
831853

832854
int dir_fd = dirfd(dir);
833-
bool allow_stale_unlink = dir_fd >= 0 && run_dir_allows_stale_unlink(run_dir);
855+
bool allow_stale_unlink = dir_fd >= 0 && dir_fd_allows_stale_unlink(dir_fd);
834856

835857
struct dirent *ent;
836858
while ((ent = readdir(dir)) != NULL) {
@@ -852,7 +874,7 @@ void nipc_shm_cleanup_stale(const char *run_dir, const char *service_name)
852874

853875
/* check_shm_stale unlinks stale files and returns:
854876
* 0 = stale (unlinked), +1 = live, -1 = gone, -2 = invalid (unlinked) */
855-
check_shm_stale(path, dir_fd, ent->d_name, allow_stale_unlink);
877+
check_shm_stale(dir_fd, ent->d_name, allow_stale_unlink);
856878
}
857879

858880
closedir(dir);

0 commit comments

Comments
 (0)