Skip to content
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

fix: prevent SQL injection in overwrite.c #354

Closed
wants to merge 12 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 74 additions & 3 deletions app/utils/overwrite.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <zsv/utils/overwrite.h>
#include <zsv/utils/cache.h>
#include <zsv/utils/file.h>
#include <zsv/utils/string.h>

#define zsv_overwrite_sqlite3_prefix "sqlite3://"
#define zsv_overwrite_sql_prefix "sql="
Expand Down Expand Up @@ -116,9 +117,72 @@ enum zsv_status zsv_overwrite_next(void *h, struct zsv_overwrite_data *odata) {
return ctx->next(ctx, odata);
}

static const char *get_safe_sql_query(sqlite3 *db, const char *user_sql) {
static const char *default_query = "select row, col, val, timestamp, author from overwrites order by row, col";

// Handle NULL or empty input
if (!user_sql || !*user_sql)
return default_query;

if (!db)
return default_query;

sqlite3_stmt *stmt = NULL;
const char *tail = NULL;

// Try to prepare the statement (parse SQL)
int rc = sqlite3_prepare_v2(db, user_sql, -1, &stmt, &tail);

// Check if parsing succeeded and we got a valid statement
if (rc != SQLITE_OK || !stmt) {
if (stmt)
sqlite3_finalize(stmt);
return default_query;
}

// Verify it's a single statement (no additional statements in tail)
if (tail && *tail != '\0') {
sqlite3_finalize(stmt);
return default_query;
}

// Verify it's a read-only (SELECT) statement
if (!sqlite3_stmt_readonly(stmt)) {
sqlite3_finalize(stmt);
return default_query;
}

// Verify required columns are present
int col_count = sqlite3_column_count(stmt);
int has_row = 0, has_column = 0, has_value = 0;

for (int i = 0; i < col_count; i++) {
const char *col_name = sqlite3_column_name(stmt, i);
if (!col_name)
continue;

if (strcmp(col_name, "row") == 0)
has_row = 1;
else if (strcmp(col_name, "column") == 0 || strcmp(col_name, "col") == 0)
has_column = 1;
else if (strcmp(col_name, "value") == 0 || strcmp(col_name, "val") == 0)
has_value = 1;
}

sqlite3_finalize(stmt);

// Ensure required columns are present (timestamp is optional)
if (!has_row || !has_column || !has_value)
return default_query;

return user_sql;
}

static enum zsv_status zsv_overwrite_init_sqlite3(struct zsv_overwrite_ctx *ctx, const char *source, size_t len) {
char ok = 0;
size_t pfx_len;
const char *user_sql = NULL;

if (len > (pfx_len = strlen(zsv_overwrite_sqlite3_prefix)) &&
!memcmp(source, zsv_overwrite_sqlite3_prefix, pfx_len)) {
ctx->sqlite3.filename = malloc(len - pfx_len + 1);
Expand All @@ -130,15 +194,15 @@ static enum zsv_status zsv_overwrite_init_sqlite3(struct zsv_overwrite_ctx *ctx,
q++;
const char *sql = strstr(q, zsv_overwrite_sql_prefix);
if (sql)
ctx->sqlite3.sql = sql + strlen(zsv_overwrite_sql_prefix);
user_sql = sql + strlen(zsv_overwrite_sql_prefix);
}

if (!ctx->sqlite3.filename || !*ctx->sqlite3.filename) {
fprintf(stderr, "Missing sqlite3 file name\n");
return zsv_status_error;
}

if (!ctx->sqlite3.sql || !*ctx->sqlite3.sql) {
if (!user_sql || !*user_sql) {
// to do: detect it from the db
fprintf(stderr, "Missing sql select statement for sqlite3 overwrite data e.g.:\n"
" select row, column, value from overwrites order by row, column\n");
Expand All @@ -147,7 +211,7 @@ static enum zsv_status zsv_overwrite_init_sqlite3(struct zsv_overwrite_ctx *ctx,
ok = 1;
} else if (len > strlen(".sqlite3") && !strcmp(source + len - strlen(".sqlite3"), ".sqlite3")) {
ctx->sqlite3.filename = strdup(source);
ctx->sqlite3.sql = "select * from overwrites order by row, column";
user_sql = "select * from overwrites order by row, column";
ok = 1;
}

Expand All @@ -158,6 +222,13 @@ static enum zsv_status zsv_overwrite_init_sqlite3(struct zsv_overwrite_ctx *ctx,
return zsv_status_error;
}

// Now that we have a valid db connection, validate the SQL
ctx->sqlite3.sql = get_safe_sql_query(ctx->sqlite3.db, user_sql);
if (!ctx->sqlite3.sql) {
fprintf(stderr, "Invalid or unsafe SQL query\n");
return zsv_status_error;
}

rc = sqlite3_prepare_v2(ctx->sqlite3.db, ctx->sqlite3.sql, -1, &ctx->sqlite3.stmt, NULL);
if (rc != SQLITE_OK || !ctx->sqlite3.stmt) {
fprintf(stderr, "%s\n", sqlite3_errmsg(ctx->sqlite3.db));
Expand Down