Skip to content

Commit 77923d6

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 - cypher: Add malloc/calloc NULL checks in parse_props, parse_rel_types, parse_in_condition, and parse_case_expr to prevent OOM crashes - store: Add sqlite3_prepare_v2 return code checks at 3 sites in cbm_store_schema_info and collect_pkg_names to prevent NULL stmt dereference on DB corruption
1 parent 1d30971 commit 77923d6

File tree

3 files changed

+57
-3
lines changed

3 files changed

+57
-3
lines changed

src/cypher/cypher.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ 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) {
97+
if (blen >= max_blen) { (*pos)++; continue; }
9698
if (input[*pos] == '\\' && *pos + SKIP_ONE < len) {
9799
(*pos)++;
98100
switch (input[*pos]) {
@@ -469,6 +471,9 @@ static int parse_props(parser_t *p, cbm_prop_filter_t **out, int *count) {
469471
int cap = CYP_INIT_CAP4;
470472
int n = 0;
471473
cbm_prop_filter_t *arr = malloc(cap * sizeof(cbm_prop_filter_t));
474+
if (!arr) {
475+
return CBM_NOT_FOUND;
476+
}
472477

473478
while (!check(p, TOK_RBRACE) && !check(p, TOK_EOF)) {
474479
const cbm_token_t *key = expect(p, TOK_IDENT);
@@ -569,6 +574,9 @@ static int parse_rel_types(parser_t *p, cbm_rel_pattern_t *out) {
569574
int cap = CYP_INIT_CAP4;
570575
int n = 0;
571576
const char **types = malloc(cap * sizeof(const char *));
577+
if (!types) {
578+
return CBM_NOT_FOUND;
579+
}
572580

573581
const cbm_token_t *t = expect(p, TOK_IDENT);
574582
if (!t) {
@@ -762,6 +770,12 @@ static cbm_expr_t *parse_in_list(parser_t *p, cbm_condition_t *c) {
762770
int vcap = CYP_INIT_CAP8;
763771
int vn = 0;
764772
const char **vals = malloc(vcap * sizeof(const char *));
773+
if (!vals) {
774+
free((void *)c->variable);
775+
free((void *)c->property);
776+
free((void *)c->op);
777+
return NULL;
778+
}
765779
while (!check(p, TOK_RBRACKET) && !check(p, TOK_EOF)) {
766780
if (vn > 0) {
767781
match(p, TOK_COMMA);
@@ -1061,8 +1075,15 @@ static const char *parse_value_literal(parser_t *p) {
10611075
static cbm_case_expr_t *parse_case_expr(parser_t *p) {
10621076
/* CASE already consumed */
10631077
cbm_case_expr_t *kase = calloc(CBM_ALLOC_ONE, sizeof(cbm_case_expr_t));
1078+
if (!kase) {
1079+
return NULL;
1080+
}
10641081
int bcap = CYP_INIT_CAP4;
10651082
kase->branches = malloc(bcap * sizeof(cbm_case_branch_t));
1083+
if (!kase->branches) {
1084+
free(kase);
1085+
return NULL;
1086+
}
10661087

10671088
while (check(p, TOK_WHEN)) {
10681089
advance(p);

src/store/store.c

Lines changed: 9 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,9 @@ 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+
return CBM_NOT_FOUND;
2584+
}
25812585
bind_text(stmt, SKIP_ONE, project);
25822586

25832587
int cap = ST_INIT_CAP_8;
@@ -3283,7 +3287,9 @@ static bool pkg_in_list(const char *pkg, char **list, int count) {
32833287
static int collect_pkg_names(cbm_store_t *s, const char *sql, const char *project, char **pkgs,
32843288
int max_pkgs) {
32853289
sqlite3_stmt *stmt = NULL;
3286-
sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL);
3290+
if (sqlite3_prepare_v2(s->db, sql, CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK || !stmt) {
3291+
return 0;
3292+
}
32873293
bind_text(stmt, SKIP_ONE, project);
32883294
int count = 0;
32893295
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)