mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(pipeline): prevent stack-buffer-overflow in append_args_json
A call carrying enough long arguments drove append_args_json()'s running position past the fixed CBM_SZ_2K `props` stack buffer in emit_normal_calls_edge(): format_call_arg() returns snprintf's *untruncated* length, so `pos += (size_t)n` could exceed `bufsize`, after which the trailing `buf[pos] = '\0'` (and `buf[pos++] = ']'`) wrote out of bounds. The stack canary caught it as SIGABRT, so full-repo indexing of large TypeScript codebases crashed the server in the parallel resolve pass (emit_service_edge -> emit_normal_calls_edge -> finalize_and_emit -> append_args_json). Confirmed with AddressSanitizer: stack-buffer-overflow WRITE at pass_parallel.c:1124, 'props' (2048 B). Fix: when an argument does not fully fit, roll back to before its separator and stop appending (atomic field, matching append_json_string's behaviour), so `pos` can never advance past the buffer. Add regression test parallel_args_json_no_overflow: indexes a fixture whose single call carries 60 long string args (args JSON well past 2 KB); under the ASan test build it aborts without this fix and passes with it. Signed-off-by: Andrius Skerla <1492322+rainder@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e599df1d56
commit
74d15a6064
2 files changed
+58
-2
No files matched your search
@@ -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++] = ']';
|
||||
|
||||
@@ -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) {
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user