Skip to content

Commit c58796c

Browse files
committed
fix(cypher,store): prevent crashes from buffer overflow, OOM, and NULL stmts
- cypher: Add bounds check in lex_string_literal to prevent stack buffer overflow on string literals >4096 bytes. Escape sequences are always parsed correctly even past the truncation boundary. - cypher: Add malloc/calloc NULL checks in parse_props, parse_rel_types, parse_in_condition, and parse_case_expr — both initial allocation and safe_realloc growth paths — to prevent OOM crashes. - store: Add sqlite3_prepare_v2 return code checks at 3 sites in cbm_store_schema_info and collect_pkg_names. Schema function cleans up partially populated output on failure. collect_pkg_names returns CBM_NOT_FOUND (not 0) to distinguish errors from empty results.
1 parent 1d30971 commit c58796c

3 files changed

Lines changed: 92 additions & 17 deletions

File tree

src/cypher/cypher.c

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,25 +92,30 @@ static void lex_string_literal(const char *input, int len, int *pos, char quote,
9292
int start = *pos;
9393
char buf[CBM_SZ_4K];
9494
int blen = 0;
95+
const int max_blen = CBM_SZ_4K - 1;
9596
while (*pos < len && input[*pos] != quote) {
9697
if (input[*pos] == '\\' && *pos + SKIP_ONE < len) {
9798
(*pos)++;
98-
switch (input[*pos]) {
99-
case 'n':
100-
buf[blen++] = '\n';
101-
break;
102-
case 't':
103-
buf[blen++] = '\t';
104-
break;
105-
case '\\':
106-
buf[blen++] = '\\';
107-
break;
108-
default:
109-
buf[blen++] = input[*pos];
110-
break;
99+
if (blen < max_blen) {
100+
switch (input[*pos]) {
101+
case 'n':
102+
buf[blen++] = '\n';
103+
break;
104+
case 't':
105+
buf[blen++] = '\t';
106+
break;
107+
case '\\':
108+
buf[blen++] = '\\';
109+
break;
110+
default:
111+
buf[blen++] = input[*pos];
112+
break;
113+
}
111114
}
112115
} else {
113-
buf[blen++] = input[*pos];
116+
if (blen < max_blen) {
117+
buf[blen++] = input[*pos];
118+
}
114119
}
115120
(*pos)++;
116121
}
@@ -469,6 +474,9 @@ static int parse_props(parser_t *p, cbm_prop_filter_t **out, int *count) {
469474
int cap = CYP_INIT_CAP4;
470475
int n = 0;
471476
cbm_prop_filter_t *arr = malloc(cap * sizeof(cbm_prop_filter_t));
477+
if (!arr) {
478+
return CBM_NOT_FOUND;
479+
}
472480

473481
while (!check(p, TOK_RBRACE) && !check(p, TOK_EOF)) {
474482
const cbm_token_t *key = expect(p, TOK_IDENT);
@@ -489,6 +497,9 @@ static int parse_props(parser_t *p, cbm_prop_filter_t **out, int *count) {
489497
if (n >= cap) {
490498
cap *= PAIR_LEN;
491499
arr = safe_realloc(arr, cap * sizeof(cbm_prop_filter_t));
500+
if (!arr) {
501+
return CBM_NOT_FOUND;
502+
}
492503
}
493504
arr[n].key = heap_strdup(key->text);
494505
arr[n].value = heap_strdup(val->text);
@@ -569,6 +580,9 @@ static int parse_rel_types(parser_t *p, cbm_rel_pattern_t *out) {
569580
int cap = CYP_INIT_CAP4;
570581
int n = 0;
571582
const char **types = malloc(cap * sizeof(const char *));
583+
if (!types) {
584+
return CBM_NOT_FOUND;
585+
}
572586

573587
const cbm_token_t *t = expect(p, TOK_IDENT);
574588
if (!t) {
@@ -589,6 +603,9 @@ static int parse_rel_types(parser_t *p, cbm_rel_pattern_t *out) {
589603
if (n >= cap) {
590604
cap *= PAIR_LEN;
591605
types = safe_realloc(types, cap * sizeof(const char *));
606+
if (!types) {
607+
return CBM_NOT_FOUND;
608+
}
592609
}
593610
types[n++] = heap_strdup(t->text);
594611
}
@@ -762,6 +779,12 @@ static cbm_expr_t *parse_in_list(parser_t *p, cbm_condition_t *c) {
762779
int vcap = CYP_INIT_CAP8;
763780
int vn = 0;
764781
const char **vals = malloc(vcap * sizeof(const char *));
782+
if (!vals) {
783+
free((void *)c->variable);
784+
free((void *)c->property);
785+
free((void *)c->op);
786+
return NULL;
787+
}
765788
while (!check(p, TOK_RBRACKET) && !check(p, TOK_EOF)) {
766789
if (vn > 0) {
767790
match(p, TOK_COMMA);
@@ -770,6 +793,12 @@ static cbm_expr_t *parse_in_list(parser_t *p, cbm_condition_t *c) {
770793
if (vn >= vcap) {
771794
vcap *= PAIR_LEN;
772795
vals = safe_realloc(vals, vcap * sizeof(const char *));
796+
if (!vals) {
797+
free((void *)c->variable);
798+
free((void *)c->property);
799+
free((void *)c->op);
800+
return NULL;
801+
}
773802
}
774803
vals[vn++] = heap_strdup(advance(p)->text);
775804
} else {
@@ -1061,8 +1090,15 @@ static const char *parse_value_literal(parser_t *p) {
10611090
static cbm_case_expr_t *parse_case_expr(parser_t *p) {
10621091
/* CASE already consumed */
10631092
cbm_case_expr_t *kase = calloc(CBM_ALLOC_ONE, sizeof(cbm_case_expr_t));
1093+
if (!kase) {
1094+
return NULL;
1095+
}
10641096
int bcap = CYP_INIT_CAP4;
10651097
kase->branches = malloc(bcap * sizeof(cbm_case_branch_t));
1098+
if (!kase->branches) {
1099+
free(kase);
1100+
return NULL;
1101+
}
10661102

10671103
while (check(p, TOK_WHEN)) {
10681104
advance(p);
@@ -1075,6 +1111,11 @@ static cbm_case_expr_t *parse_case_expr(parser_t *p) {
10751111
if (kase->branch_count >= bcap) {
10761112
bcap *= PAIR_LEN;
10771113
kase->branches = safe_realloc(kase->branches, bcap * sizeof(cbm_case_branch_t));
1114+
if (!kase->branches) {
1115+
expr_free(when);
1116+
free(kase);
1117+
return NULL;
1118+
}
10781119
}
10791120
kase->branches[kase->branch_count++] =
10801121
(cbm_case_branch_t){.when_expr = when, .then_val = then_val};

src/store/store.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2552,7 +2552,9 @@ int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t
25522552
const char *sql = "SELECT label, COUNT(*) FROM nodes WHERE project = ?1 GROUP BY label "
25532553
"ORDER BY COUNT(*) DESC;";
25542554
sqlite3_stmt *stmt = NULL;
2555-
sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL);
2555+
if (sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK || !stmt) {
2556+
return CBM_NOT_FOUND;
2557+
}
25562558
bind_text(stmt, SKIP_ONE, project);
25572559

25582560
int cap = ST_INIT_CAP_8;
@@ -2577,7 +2579,10 @@ int cbm_store_get_schema(cbm_store_t *s, const char *project, cbm_schema_info_t
25772579
const char *sql = "SELECT type, COUNT(*) FROM edges WHERE project = ?1 GROUP BY type ORDER "
25782580
"BY COUNT(*) DESC;";
25792581
sqlite3_stmt *stmt = NULL;
2580-
sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL);
2582+
if (sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK || !stmt) {
2583+
cbm_store_schema_free(out);
2584+
return CBM_NOT_FOUND;
2585+
}
25812586
bind_text(stmt, SKIP_ONE, project);
25822587

25832588
int cap = ST_INIT_CAP_8;
@@ -3283,7 +3288,9 @@ static bool pkg_in_list(const char *pkg, char **list, int count) {
32833288
static int collect_pkg_names(cbm_store_t *s, const char *sql, const char *project, char **pkgs,
32843289
int max_pkgs) {
32853290
sqlite3_stmt *stmt = NULL;
3286-
sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL);
3291+
if (sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK || !stmt) {
3292+
return CBM_NOT_FOUND;
3293+
}
32873294
bind_text(stmt, SKIP_ONE, project);
32883295
int count = 0;
32893296
while (sqlite3_step(stmt) == SQLITE_ROW && count < max_pkgs) {

tests/test_cypher.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,32 @@ TEST(cypher_lex_single_quote_string) {
7878
PASS();
7979
}
8080

81+
TEST(cypher_lex_string_overflow) {
82+
/* Build a string literal longer than 4096 bytes to verify we don't
83+
* overflow the stack buffer in lex_string_literal. */
84+
const int big = 5000;
85+
/* query: "AAAA...A" (quotes included) */
86+
char *query = malloc(big + 3); /* quote + big chars + quote + NUL */
87+
ASSERT_NOT_NULL(query);
88+
query[0] = '"';
89+
memset(query + 1, 'A', big);
90+
query[big + 1] = '"';
91+
query[big + 2] = '\0';
92+
93+
cbm_lex_result_t r = {0};
94+
int rc = cbm_lex(query, &r);
95+
ASSERT_EQ(rc, 0);
96+
ASSERT_NULL(r.error);
97+
ASSERT_GTE(r.count, 1);
98+
ASSERT_EQ(r.tokens[0].type, TOK_STRING);
99+
/* The string should be truncated to CBM_SZ_4K - 1 (4095) characters. */
100+
ASSERT_EQ((int)strlen(r.tokens[0].text), 4095);
101+
102+
cbm_lex_free(&r);
103+
free(query);
104+
PASS();
105+
}
106+
81107
TEST(cypher_lex_number) {
82108
cbm_lex_result_t r = {0};
83109
int rc = cbm_lex("42 3.14", &r);
@@ -2064,6 +2090,7 @@ SUITE(cypher) {
20642090
RUN_TEST(cypher_lex_relationship);
20652091
RUN_TEST(cypher_lex_string_literal);
20662092
RUN_TEST(cypher_lex_single_quote_string);
2093+
RUN_TEST(cypher_lex_string_overflow);
20672094
RUN_TEST(cypher_lex_number);
20682095
RUN_TEST(cypher_lex_operators);
20692096
RUN_TEST(cypher_lex_keywords_case_insensitive);

0 commit comments

Comments
 (0)