-
Notifications
You must be signed in to change notification settings - Fork 143
fix(cypher,store): prevent crashes from buffer overflow, OOM, and NULL stmts #206
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
base: main
Are you sure you want to change the base?
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; | ||||||||
| } | ||||||||
|
Comment on lines
497
to
+507
|
||||||||
| arr = tmp; | ||||||||
| cap = new_cap; | ||||||||
| } | ||||||||
| arr[n].key = heap_strdup(key->text); | ||||||||
| arr[n].value = heap_strdup(val->text); | ||||||||
|
Comment on lines
+508
to
512
|
||||||||
|
|
@@ -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; | ||||||||
| } | ||||||||
|
Comment on lines
587
to
+592
|
||||||||
|
|
||||||||
| 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; | ||||||||
| } | ||||||||
|
Comment on lines
610
to
+619
|
||||||||
| types = (const char **)tmp; | ||||||||
| cap = new_cap; | ||||||||
| } | ||||||||
| types[n++] = heap_strdup(t->text); | ||||||||
| } | ||||||||
|
Comment on lines
+620
to
624
|
||||||||
|
|
@@ -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; | ||||||||
|
Comment on lines
792
to
+799
|
||||||||
| } | ||||||||
| 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 { | ||||||||
|
Comment on lines
+818
to
823
|
||||||||
|
|
@@ -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); | ||||||||
|
||||||||
| expr_free(kase->branches[i].when_expr); | |
| expr_free(kase->branches[i].when_expr); | |
| free((void *)kase->branches[i].then_val); |
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 branch growth, safe_realloc frees the existing kase->branches array on failure; returning here then leaks all previously parsed when_expr trees and then_val strings already stored in the old branches (since you no longer have the array to iterate). Switch to a non-destructive realloc pattern and, on failure, call free_case_expr(kase) (or equivalent partial cleanup) before returning.
| 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; | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+2555
to
+2560
|
||||||||||||||
| 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 (stmt) { | ||||||||||||||
| sqlite3_finalize(stmt); | ||||||||||||||
| } | ||||||||||||||
| cbm_store_schema_free(out); | ||||||||||||||
| return CBM_NOT_FOUND; | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+2585
to
+2591
|
||||||||||||||
| bind_text(stmt, SKIP_ONE, project); | ||||||||||||||
|
|
||||||||||||||
| int cap = ST_INIT_CAP_8; | ||||||||||||||
|
|
@@ -3283,7 +3294,12 @@ static bool pkg_in_list(const char *pkg, char **list, int count) { | |||||||||||||
| static int collect_pkg_names(cbm_store_t *s, const char *sql, const char *project, char **pkgs, | ||||||||||||||
| int max_pkgs) { | ||||||||||||||
| 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) { | |
| int rc = sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL); | |
| if (rc != SQLITE_OK || !stmt) { | |
| if (stmt != NULL) { | |
| sqlite3_finalize(stmt); | |
| } |
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.
collect_pkg_names returns CBM_NOT_FOUND on sqlite3_prepare_v2 failure, which is the same numeric value as CBM_STORE_ERR (-1) but semantically inconsistent with other store helpers that return CBM_STORE_ERR and call store_set_error_sqlite(). Consider returning CBM_STORE_ERR (or 0 if you intend “no pkgs”) and setting the store error so downstream callers can diagnose DB corruption.
| return CBM_NOT_FOUND; | |
| store_set_error_sqlite(s, "sqlite3_prepare_v2 failed in collect_pkg_names"); | |
| return CBM_STORE_ERR; |
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.
This adds an initial
mallocNULL-check, but the function still callssafe_reallocandheap_strduplater without checking for NULL. Sincesafe_reallocreturns NULL (and frees the old pointer), an OOM during growth or key/value duplication can still lead to NULL dereferences (e.g., laterstrcmpon a NULLprops[i].value). Consider propagating an error if any allocation/duplication fails and freeing partially-builtarr.