Skip to content

Commit 6a6127c

Browse files
committed
Fix SQL injection in store search/BFS and argument injection in HTTP server
- cbm_store_search(): exclude_labels were interpolated as '%s' literals into SQL. Replaced with ?N parameterized placeholders via BIND_TEXT(). - cbm_store_bfs(): edge_types IN clause used '%s' interpolation. Replaced with ?N placeholders and bind_text() for both the CTE query and the follow-up edge collection query. - index_thread_fn(): root_path was embedded raw into a JSON string for the indexer subprocess. Added JSON escaping for " and \ before interpolation.
1 parent c38853f commit 6a6127c

2 files changed

Lines changed: 43 additions & 9 deletions

File tree

src/store/store.c

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,7 +1889,7 @@ int cbm_store_search(cbm_store_t *s, const cbm_search_params_t *params, cbm_sear
18891889
BIND_TEXT(like_pattern);
18901890
}
18911891

1892-
/* Exclude labels: add NOT IN clause directly (no bind params — values are code-provided) */
1892+
/* Exclude labels: use parameterized placeholders to prevent SQL injection */
18931893
if (params->exclude_labels) {
18941894
char excl_clause[512] = "n.label NOT IN (";
18951895
int elen = (int)strlen(excl_clause);
@@ -1900,11 +1900,12 @@ int cbm_store_search(cbm_store_t *s, const cbm_search_params_t *params, cbm_sear
19001900
elen = (int)sizeof(excl_clause) - 1;
19011901
}
19021902
}
1903-
elen += snprintf(excl_clause + elen, sizeof(excl_clause) - (size_t)elen, "'%s'",
1904-
params->exclude_labels[i]);
1903+
elen += snprintf(excl_clause + elen, sizeof(excl_clause) - (size_t)elen, "?%d",
1904+
bind_idx + 1);
19051905
if (elen >= (int)sizeof(excl_clause)) {
19061906
elen = (int)sizeof(excl_clause) - 1;
19071907
}
1908+
BIND_TEXT(params->exclude_labels[i]);
19081909
}
19091910
snprintf(excl_clause + elen, sizeof(excl_clause) - (size_t)elen, ")");
19101911
ADD_WHERE(excl_clause);
@@ -2040,8 +2041,9 @@ int cbm_store_bfs(cbm_store_t *s, int64_t start_id, const char *direction, const
20402041
}
20412042
out->root = root;
20422043

2043-
/* Build edge type IN clause */
2044-
char types_clause[512] = "'CALLS'";
2044+
/* Build edge type IN clause with parameterized placeholders */
2045+
char types_clause[512] = "?1";
2046+
const char *default_edge_type = "CALLS";
20452047
if (edge_type_count > 0) {
20462048
int tlen = 0;
20472049
for (int i = 0; i < edge_type_count; i++) {
@@ -2051,8 +2053,8 @@ int cbm_store_bfs(cbm_store_t *s, int64_t start_id, const char *direction, const
20512053
tlen = (int)sizeof(types_clause) - 1;
20522054
}
20532055
}
2054-
tlen += snprintf(types_clause + tlen, sizeof(types_clause) - (size_t)tlen, "'%s'",
2055-
edge_types[i]);
2056+
tlen += snprintf(types_clause + tlen, sizeof(types_clause) - (size_t)tlen, "?%d",
2057+
i + 1);
20562058
if (tlen >= (int)sizeof(types_clause)) {
20572059
tlen = (int)sizeof(types_clause) - 1;
20582060
}
@@ -2099,6 +2101,15 @@ int cbm_store_bfs(cbm_store_t *s, int64_t start_id, const char *direction, const
20992101
return CBM_STORE_ERR;
21002102
}
21012103

2104+
/* Bind edge type parameters */
2105+
if (edge_type_count > 0) {
2106+
for (int i = 0; i < edge_type_count; i++) {
2107+
bind_text(stmt, i + 1, edge_types[i]);
2108+
}
2109+
} else {
2110+
bind_text(stmt, 1, default_edge_type);
2111+
}
2112+
21022113
int cap = 16;
21032114
int n = 0;
21042115
cbm_node_hop_t *visited = malloc(cap * sizeof(cbm_node_hop_t));
@@ -2147,6 +2158,15 @@ int cbm_store_bfs(cbm_store_t *s, int64_t start_id, const char *direction, const
21472158
sqlite3_stmt *estmt = NULL;
21482159
rc = sqlite3_prepare_v2(s->db, edge_sql, -1, &estmt, NULL);
21492160
if (rc == SQLITE_OK) {
2161+
/* Bind edge type parameters for the edge query */
2162+
if (edge_type_count > 0) {
2163+
for (int i = 0; i < edge_type_count; i++) {
2164+
bind_text(estmt, i + 1, edge_types[i]);
2165+
}
2166+
} else {
2167+
bind_text(estmt, 1, default_edge_type);
2168+
}
2169+
21502170
int ecap = 8;
21512171
int en = 0;
21522172
cbm_edge_info_t *edges = malloc(ecap * sizeof(cbm_edge_info_t));

src/ui/http_server.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -604,8 +604,22 @@ static void *index_thread_fn(void *arg) {
604604
}
605605

606606
char log_file[256];
607-
char json_arg[1200];
608-
snprintf(json_arg, sizeof(json_arg), "{\"repo_path\":\"%s\"}", job->root_path);
607+
608+
/* JSON-escape root_path to prevent injection via double-quotes or backslashes */
609+
char escaped_path[2048];
610+
{
611+
const char *s = job->root_path;
612+
size_t j = 0;
613+
for (; *s && j < sizeof(escaped_path) - 2; s++) {
614+
if (*s == '"' || *s == '\\') {
615+
escaped_path[j++] = '\\';
616+
}
617+
escaped_path[j++] = *s;
618+
}
619+
escaped_path[j] = '\0';
620+
}
621+
char json_arg[4096];
622+
snprintf(json_arg, sizeof(json_arg), "{\"repo_path\":\"%s\"}", escaped_path);
609623

610624
#ifdef _WIN32
611625
snprintf(log_file, sizeof(log_file), "%s\\cbm_index_%d.log",

0 commit comments

Comments
 (0)