-
Notifications
You must be signed in to change notification settings - Fork 0
fix(cypher,store): prevent crashes from buffer overflow, OOM, and NULL stmts #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,25 +92,30 @@ static void lex_string_literal(const char *input, int len, int *pos, char quote, | |
| int start = *pos; | ||
| char buf[CBM_SZ_4K]; | ||
| int blen = 0; | ||
| const int max_blen = CBM_SZ_4K - 1; | ||
| while (*pos < len && input[*pos] != quote) { | ||
| if (input[*pos] == '\\' && *pos + SKIP_ONE < len) { | ||
| (*pos)++; | ||
| switch (input[*pos]) { | ||
| case 'n': | ||
| buf[blen++] = '\n'; | ||
| break; | ||
| case 't': | ||
| buf[blen++] = '\t'; | ||
| break; | ||
| case '\\': | ||
| buf[blen++] = '\\'; | ||
| break; | ||
| default: | ||
| buf[blen++] = input[*pos]; | ||
| break; | ||
| if (blen < max_blen) { | ||
| switch (input[*pos]) { | ||
| case 'n': | ||
| buf[blen++] = '\n'; | ||
| break; | ||
| case 't': | ||
| buf[blen++] = '\t'; | ||
| break; | ||
| case '\\': | ||
| buf[blen++] = '\\'; | ||
| break; | ||
| default: | ||
| buf[blen++] = input[*pos]; | ||
| break; | ||
| } | ||
| } | ||
| } else { | ||
| buf[blen++] = input[*pos]; | ||
| if (blen < max_blen) { | ||
| buf[blen++] = input[*pos]; | ||
| } | ||
| } | ||
| (*pos)++; | ||
| } | ||
|
|
@@ -469,6 +474,9 @@ static int parse_props(parser_t *p, cbm_prop_filter_t **out, int *count) { | |
| int cap = CYP_INIT_CAP4; | ||
| int n = 0; | ||
| cbm_prop_filter_t *arr = malloc(cap * sizeof(cbm_prop_filter_t)); | ||
| if (!arr) { | ||
| return CBM_NOT_FOUND; | ||
| } | ||
|
|
||
| while (!check(p, TOK_RBRACE) && !check(p, TOK_EOF)) { | ||
| const cbm_token_t *key = expect(p, TOK_IDENT); | ||
|
|
@@ -487,8 +495,18 @@ static int parse_props(parser_t *p, cbm_prop_filter_t **out, int *count) { | |
| } | ||
|
|
||
| if (n >= cap) { | ||
| cap *= PAIR_LEN; | ||
| arr = safe_realloc(arr, cap * sizeof(cbm_prop_filter_t)); | ||
| int new_cap = cap * PAIR_LEN; | ||
| void *tmp = realloc(arr, new_cap * sizeof(cbm_prop_filter_t)); | ||
| if (!tmp) { | ||
| for (int i = 0; i < n; i++) { | ||
| free((void *)arr[i].key); | ||
| free((void *)arr[i].value); | ||
| } | ||
| free(arr); | ||
| return CBM_NOT_FOUND; | ||
| } | ||
| arr = tmp; | ||
| cap = new_cap; | ||
| } | ||
| arr[n].key = heap_strdup(key->text); | ||
| arr[n].value = heap_strdup(val->text); | ||
|
|
@@ -569,6 +587,9 @@ static int parse_rel_types(parser_t *p, cbm_rel_pattern_t *out) { | |
| int cap = CYP_INIT_CAP4; | ||
| int n = 0; | ||
| const char **types = malloc(cap * sizeof(const char *)); | ||
| if (!types) { | ||
| return CBM_NOT_FOUND; | ||
| } | ||
|
|
||
| const cbm_token_t *t = expect(p, TOK_IDENT); | ||
| if (!t) { | ||
|
|
@@ -587,8 +608,17 @@ static int parse_rel_types(parser_t *p, cbm_rel_pattern_t *out) { | |
| return CBM_NOT_FOUND; | ||
| } | ||
| if (n >= cap) { | ||
| cap *= PAIR_LEN; | ||
| types = safe_realloc(types, cap * sizeof(const char *)); | ||
| int new_cap = cap * PAIR_LEN; | ||
| void *tmp = realloc(types, new_cap * sizeof(const char *)); | ||
| if (!tmp) { | ||
| for (int i = 0; i < n; i++) { | ||
| free((void *)types[i]); | ||
| } | ||
| free(types); | ||
| return CBM_NOT_FOUND; | ||
| } | ||
| types = (const char **)tmp; | ||
| cap = new_cap; | ||
| } | ||
| types[n++] = heap_strdup(t->text); | ||
| } | ||
|
|
@@ -762,14 +792,32 @@ static cbm_expr_t *parse_in_list(parser_t *p, cbm_condition_t *c) { | |
| int vcap = CYP_INIT_CAP8; | ||
| int vn = 0; | ||
| const char **vals = malloc(vcap * sizeof(const char *)); | ||
| if (!vals) { | ||
| free((void *)c->variable); | ||
| free((void *)c->property); | ||
| free((void *)c->op); | ||
| return NULL; | ||
| } | ||
| while (!check(p, TOK_RBRACKET) && !check(p, TOK_EOF)) { | ||
| if (vn > 0) { | ||
| match(p, TOK_COMMA); | ||
| } | ||
| if (check(p, TOK_STRING) || check(p, TOK_NUMBER)) { | ||
| if (vn >= vcap) { | ||
| vcap *= PAIR_LEN; | ||
| vals = safe_realloc(vals, vcap * sizeof(const char *)); | ||
| int new_vcap = vcap * PAIR_LEN; | ||
| void *tmp = realloc((void *)vals, new_vcap * sizeof(const char *)); | ||
| if (!tmp) { | ||
| for (int i = 0; i < vn; i++) { | ||
| free((void *)vals[i]); | ||
| } | ||
| free((void *)vals); | ||
| free((void *)c->variable); | ||
| free((void *)c->property); | ||
| free((void *)c->op); | ||
| return NULL; | ||
| } | ||
| vals = (const char **)tmp; | ||
| vcap = new_vcap; | ||
| } | ||
| vals[vn++] = heap_strdup(advance(p)->text); | ||
| } else { | ||
|
|
@@ -1061,8 +1109,15 @@ static const char *parse_value_literal(parser_t *p) { | |
| static cbm_case_expr_t *parse_case_expr(parser_t *p) { | ||
| /* CASE already consumed */ | ||
| cbm_case_expr_t *kase = calloc(CBM_ALLOC_ONE, sizeof(cbm_case_expr_t)); | ||
| if (!kase) { | ||
| return NULL; | ||
| } | ||
| int bcap = CYP_INIT_CAP4; | ||
| kase->branches = malloc(bcap * sizeof(cbm_case_branch_t)); | ||
| if (!kase->branches) { | ||
| free(kase); | ||
| return NULL; | ||
| } | ||
|
Comment on lines
1111
to
+1120
|
||
|
|
||
| while (check(p, TOK_WHEN)) { | ||
| advance(p); | ||
|
|
@@ -1073,8 +1128,19 @@ static cbm_case_expr_t *parse_case_expr(parser_t *p) { | |
| } | ||
| const char *then_val = parse_value_literal(p); | ||
| if (kase->branch_count >= bcap) { | ||
| bcap *= PAIR_LEN; | ||
| kase->branches = safe_realloc(kase->branches, bcap * sizeof(cbm_case_branch_t)); | ||
| int new_bcap = bcap * PAIR_LEN; | ||
| void *tmp = realloc(kase->branches, new_bcap * sizeof(cbm_case_branch_t)); | ||
| if (!tmp) { | ||
| expr_free(when); | ||
| for (int i = 0; i < kase->branch_count; i++) { | ||
| expr_free(kase->branches[i].when_expr); | ||
| } | ||
| free(kase->branches); | ||
| free(kase); | ||
| return NULL; | ||
| } | ||
| kase->branches = tmp; | ||
| bcap = new_bcap; | ||
| } | ||
| kase->branches[kase->branch_count++] = | ||
| (cbm_case_branch_t){.when_expr = when, .then_val = then_val}; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -2552,7 +2552,12 @@ int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t | |||||||
| const char *sql = "SELECT label, COUNT(*) FROM nodes WHERE project = ?1 GROUP BY label " | ||||||||
| "ORDER BY COUNT(*) DESC;"; | ||||||||
| sqlite3_stmt *stmt = NULL; | ||||||||
| sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL); | ||||||||
| if (sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK || !stmt) { | ||||||||
| if (stmt) { | ||||||||
| sqlite3_finalize(stmt); | ||||||||
| } | ||||||||
| return CBM_NOT_FOUND; | ||||||||
| } | ||||||||
| bind_text(stmt, SKIP_ONE, project); | ||||||||
|
|
||||||||
| int cap = ST_INIT_CAP_8; | ||||||||
|
|
@@ -2577,7 +2582,13 @@ int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t | |||||||
| const char *sql = "SELECT type, COUNT(*) FROM edges WHERE project = ?1 GROUP BY type ORDER " | ||||||||
| "BY COUNT(*) DESC;"; | ||||||||
| sqlite3_stmt *stmt = NULL; | ||||||||
| sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL); | ||||||||
| if (sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK || !stmt) { | ||||||||
|
||||||||
| if (sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK || !stmt) { | |
| if (sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK || !stmt) { | |
| cbm_store_schema_free(out); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3. Schema error path leaks 🐞 Bug ☼ Reliability
cbm_store_get_schema() now returns early if the second sqlite3_prepare_v2 fails, but it may already have allocated out->node_labels from the first query. Callers like ui/layout3d.c only free schema when cbm_store_get_schema returns OK, so this introduces a leak on that failure path.
Agent Prompt
## Issue description
`cbm_store_get_schema()` can now return early after partially filling `out` (e.g., after allocating `out->node_labels`). Some callers only free schema on success, so this introduces a leak.
## Issue Context
The function currently has two independent query blocks and returns directly inside the second block.
## Fix Focus Areas
- src/store/store.c[2544-2605]
- src/ui/layout3d.c[441-492]
## Suggested fix
Option A (preferred): make `cbm_store_get_schema()` self-cleaning on failure:
- Replace early `return CBM_NOT_FOUND;` with `goto cleanup;`.
- In `cleanup:`, call `sqlite3_finalize(stmt)` if non-NULL, and call `cbm_store_schema_free(out)` (or manual frees) before returning an error.
Option B: update callers to always call `cbm_store_schema_free(&schema)` regardless of return code (safe because it zeros and checks fields), but still consider Option A for robustness.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Copilot
AI
Apr 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On sqlite3_prepare_v2 failure this now returns 0, which is indistinguishable from “query succeeded but returned 0 rows”. That masks database/SQL errors and can cause silent misclassification in arch_layers. Consider returning an error code (or a negative sentinel) and letting the caller decide whether to treat it as fatal vs. optional, and set the store error buffer for diagnostics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. Escaped quote truncated wrong
🐞 Bug≡ CorrectnessAgent Prompt
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools