Check SQL reads before interpreting poll targets and command fields

This commit is contained in:
evgeny committed 2026-10-27 03:41:06 +01:00
1 parent d89a86c9dd
commit cf7a3a0939
1 file changed
+47 -20
+47 -20
View File
@@ -40,11 +40,12 @@ static int poll_target(struct UTUN_INSTANCE* inst, const char* channel, const ui
"CASE WHEN json_valid(CAST(data AS TEXT)) THEN json_extract(CAST(data AS TEXT),'$.ch') END"
" FROM msg_%s WHERE message_id=?", channel);
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(inst->topo_sqlite_db, sql, -1, &st, NULL) != SQLITE_OK) return poll_db_error(inst, "target query");
sqlite3_bind_blob(st, 1, message_id, 32, SQLITE_STATIC);
int step = sqlite3_step(st), result = 0;
int step = sqlite3_prepare_v2(inst->topo_sqlite_db, sql, -1, &st, NULL);
if (step == SQLITE_OK) step = sqlite3_bind_blob(st, 1, message_id, 32, SQLITE_STATIC);
if (step == SQLITE_OK) step = sqlite3_step(st);
int result = 0;
if (step == SQLITE_DONE) result = 1;
else if (step != SQLITE_ROW) result = poll_db_error(inst, "target read");
else if (step != SQLITE_ROW) result = -1;
else {
const char* type = (const char*)sqlite3_column_text(st, 0);
if (type && !strcmp(type, "poll")) {
@@ -64,7 +65,12 @@ static int poll_target(struct UTUN_INSTANCE* inst, const char* channel, const ui
if (result < 0) DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "poll: invalid signed poll ch=%s", channel);
}
}
sqlite3_finalize(st); return result;
int final = sqlite3_finalize(st);
if ((step != SQLITE_ROW && step != SQLITE_DONE) || final != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "poll: target query failed ch=%s step=%d finalize=%d db=%s",
channel, step, final, sqlite3_errmsg(inst->topo_sqlite_db)); return -1;
}
return result;
}
/* Семантика ответа не влияет на сохранение подписанных байтов и Merkle-корень. */
@@ -117,8 +123,12 @@ int chat_feedback_snapshot(struct UTUN_INSTANCE* inst, const char* channel, cons
if (strbuf_addf(&replies, "[") < 0) goto done;
char sql[256];
snprintf(sql, sizeof(sql), "SELECT node_id,kind,ver,data FROM feedback_%s WHERE message_id=? ORDER BY node_id,kind", channel);
if (sqlite3_prepare_v2(inst->topo_sqlite_db, sql, -1, &st, NULL) != SQLITE_OK) { poll_db_error(inst, "snapshot query"); goto done; }
sqlite3_bind_blob(st, 1, message_id, 32, SQLITE_STATIC);
int rc = sqlite3_prepare_v2(inst->topo_sqlite_db, sql, -1, &st, NULL);
if (rc == SQLITE_OK) rc = sqlite3_bind_blob(st, 1, message_id, 32, SQLITE_STATIC);
if (rc != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "poll: snapshot query failed ch=%s rc=%d db=%s", channel, rc, sqlite3_errmsg(inst->topo_sqlite_db));
goto done;
}
while ((step = sqlite3_step(st)) == SQLITE_ROW) {
uint64_t author = (uint64_t)sqlite3_column_int64(st, 0), ver = (uint64_t)sqlite3_column_int64(st, 2);
uint8_t author_key[32];
@@ -148,6 +158,10 @@ int chat_feedback_snapshot(struct UTUN_INSTANCE* inst, const char* channel, cons
}
}
if (step != SQLITE_DONE) { poll_db_error(inst, "snapshot read"); goto done; }
rc = sqlite3_finalize(st); st = NULL;
if (rc != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "poll: snapshot finalize failed ch=%s rc=%d", channel, rc); goto done;
}
char hex[65]; for (unsigned i = 0; i < 32; i++) snprintf(hex + i * 2, 3, "%02x", message_id[i]);
if (strbuf_addf(&replies, "]") < 0 || strbuf_addf(&out,
"{\"ch\":\"%s\",\"message_id\":\"%s\",\"pending\":%s,\"reaction_ver\":%llu,\"reactions\":[",
@@ -177,7 +191,10 @@ int chat_feedback_snapshot(struct UTUN_INSTANCE* inst, const char* channel, cons
if (strbuf_addf(&out, "}") < 0) goto done;
*json = strbuf_detach(&out); result = 0;
done:
if (st) sqlite3_finalize(st);
if (st) {
int final = sqlite3_finalize(st);
if (final != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "poll: snapshot cleanup failed ch=%s rc=%d", channel, final);
}
strbuf_free(&replies); strbuf_free(&out);
if (result < 0) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "poll: snapshot failed ch=%s", channel);
return result;
@@ -193,8 +210,13 @@ void chat_feedback_check_message(struct UTUN_INSTANCE* inst, const char* channel
char sql[256]; snprintf(sql, sizeof(sql),
"SELECT node_id,ver,data FROM feedback_%s WHERE message_id=? AND kind=2 AND length(data)>0", channel);
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(inst->topo_sqlite_db, sql, -1, &st, NULL) != SQLITE_OK) { poll_db_error(inst, "evaluate query"); return; }
sqlite3_bind_blob(st, 1, message_id, 32, SQLITE_STATIC); int step; unsigned accepted = 0, rejected = 0;
int rc = sqlite3_prepare_v2(inst->topo_sqlite_db, sql, -1, &st, NULL);
if (rc == SQLITE_OK) rc = sqlite3_bind_blob(st, 1, message_id, 32, SQLITE_STATIC);
if (rc != SQLITE_OK) {
int final = sqlite3_finalize(st);
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "poll: evaluation query failed ch=%s rc=%d finalize=%d", channel, rc, final); return;
}
int step; unsigned accepted = 0, rejected = 0;
while ((step = sqlite3_step(st)) == SQLITE_ROW) {
uint8_t author_key[32];
enum ms_author_status status = member_sync_author_key(inst, channel,
@@ -210,20 +232,24 @@ void chat_feedback_check_message(struct UTUN_INSTANCE* inst, const char* channel
rejected++;
} else accepted++;
}
sqlite3_finalize(st);
if (step != SQLITE_DONE) { poll_db_error(inst, "evaluate read"); return; }
int final = sqlite3_finalize(st);
if (step != SQLITE_DONE || final != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "poll: evaluation read failed ch=%s step=%d finalize=%d", channel, step, final); return;
}
if (accepted || rejected) DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "poll: evaluated ch=%s valid=%u invalid=%u", channel, accepted, rejected);
}
/* Читает поле JSON с его типом. Отсутствующее поле имеет type=NULL. */
static sqlite3_stmt* poll_field(struct UTUN_INSTANCE* inst, const char* json, const char* path) {
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(inst->topo_sqlite_db, "SELECT json_type(?1,?2),json_extract(?1,?2)", -1, &st, NULL) != SQLITE_OK) {
poll_db_error(inst, "command field"); return NULL;
}
sqlite3_bind_text(st, 1, json, -1, SQLITE_STATIC); sqlite3_bind_text(st, 2, path, -1, SQLITE_STATIC);
if (sqlite3_step(st) == SQLITE_ROW) return st;
sqlite3_finalize(st); poll_db_error(inst, "command field read"); return NULL;
int rc = sqlite3_prepare_v2(inst->topo_sqlite_db, "SELECT json_type(?1,?2),json_extract(?1,?2)", -1, &st, NULL);
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, json, -1, SQLITE_STATIC);
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 2, path, -1, SQLITE_STATIC);
if (rc == SQLITE_OK) rc = sqlite3_step(st);
if (rc == SQLITE_ROW) return st;
int final = sqlite3_finalize(st);
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "poll: field query failed path=%s step=%d finalize=%d db=%s",
path, rc, final, sqlite3_errmsg(inst->topo_sqlite_db)); return NULL;
}
/* Требуем полную строку, не разрешая тихое обрезание и встроенный NUL. */
@@ -235,8 +261,9 @@ static int poll_field_text(struct UTUN_INSTANCE* inst, const char* json, const c
else if (type && !strcmp(type, "text") && text && bytes < capacity && strlen(text) == bytes) {
memcpy(out, text, bytes + 1); result = 0;
}
sqlite3_finalize(st);
if (result < 0) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "poll: invalid command field=%s", path);
int final = sqlite3_finalize(st);
if (final != SQLITE_OK) result = -1;
if (result < 0) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "poll: invalid command field=%s finalize=%d", path, final);
return result;
}