Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/pipeline/pass_parallel.c
Original file line number Diff line number Diff line change
Expand Up @@ -1108,15 +1108,22 @@ static size_t append_args_json(char *buf, size_t bufsize, size_t pos, const CBMC
pos += (size_t)n;
for (int i = 0; i < call->arg_count && pos < bufsize - CBM_ARG_JSON_GUARD; i++) {
const CBMCallArg *a = &call->args[i];
size_t mark = pos; /* rollback point (before the separator) */
if (i > 0 && pos < bufsize - SKIP_ONE) {
buf[pos++] = ',';
}
char expr_buf[CBM_SZ_128];
sanitize_expr(expr_buf, a->expr);
n = format_call_arg(buf + pos, bufsize - pos, a, expr_buf);
if (n > 0) {
pos += (size_t)n;
/* snprintf returns the UNtruncated length: if the arg did not fully
* fit, advancing pos by n would push it past buf and the buf[pos]
* writes below would overflow. Drop the arg whole (atomic field —
* keeps the array valid) and stop appending. */
if (n <= 0 || (size_t)n >= bufsize - pos) {
pos = mark;
break;
}
pos += (size_t)n;
}
if (pos < bufsize - SKIP_ONE) {
buf[pos++] = ']';
Expand Down
49 changes: 49 additions & 0 deletions tests/test_parallel.c
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,54 @@ TEST(parallel_empty_files) {
PASS();
}

/* ── Regression: args JSON must not overflow the props buffer ──────── */

/* A call with many long string arguments makes append_args_json()'s running
* position exceed the fixed CBM_SZ_2K `props` stack buffer in
* emit_normal_calls_edge(): format_call_arg() returns snprintf's UNtruncated
* length, so pos += n could run past the buffer and the trailing
* buf[pos]='\0' wrote out of bounds (stack-buffer-overflow; caught by the
* stack canary as a SIGABRT on real repos). This indexes a fixture whose
* single call carries enough long args to drive pos past 2 KB; under the
* ASan test build a regression aborts here. */
TEST(parallel_args_json_no_overflow) {
char dir[256];
snprintf(dir, sizeof(dir), "/tmp/cbm_argov_XXXXXX");
ASSERT_TRUE(cbm_mkdtemp(dir) != NULL);

char path[512];
snprintf(path, sizeof(path), "%s/app.ts", dir);
FILE *f = fopen(path, "w");
ASSERT_TRUE(f != NULL);
fputs("function sink(...xs: string[]) { return xs; }\n", f);
fputs("function caller() {\n sink(\n", f);
for (int i = 0; i < 60; i++) {
/* 100-char string literal per arg; 60 args => args JSON well past the
* 2 KB props buffer, forcing the pre-fix overshoot. */
fputs(" \"", f);
for (int j = 0; j < 100; j++)
fputc('a' + (i % 26), f);
fputs(i < 59 ? "\",\n" : "\"\n", f);
}
fputs(" );\n}\n", f);
fclose(f);

cbm_discover_opts_t opts = {.mode = CBM_MODE_FULL};
cbm_file_info_t *files = NULL;
int file_count = 0;
ASSERT_EQ(cbm_discover(dir, &opts, &files, &file_count), 0);
ASSERT_GT(file_count, 0);

cbm_gbuf_t *gbuf = run_parallel("argov-test", dir, files, file_count, 4);
ASSERT_TRUE(gbuf != NULL);
ASSERT_GT(cbm_gbuf_edge_count(gbuf), 0);

cbm_gbuf_free(gbuf);
cbm_discover_free(files, file_count);
th_rmtree(dir);
PASS();
}

/* ── Graph buffer merge tests ─────────────────────────────────────── */

TEST(gbuf_shared_ids_unique) {
Expand Down Expand Up @@ -680,6 +728,7 @@ SUITE(parallel) {
RUN_TEST(parallel_implements_parity);
RUN_TEST(parallel_total_edges);
RUN_TEST(parallel_empty_files);
RUN_TEST(parallel_args_json_no_overflow);

/* Cleanup shared state */
parity_teardown();
Expand Down
Loading