From cf7a3a093997e7dd66df50d4c59e3f1d480dcde6 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 03:41:06 +0100 Subject: [PATCH] Check SQL reads before interpreting poll targets and command fields --- src/chat/chat_poll.c | 67 +++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/src/chat/chat_poll.c b/src/chat/chat_poll.c index 51f72baa..f993a1fc 100644 --- a/src/chat/chat_poll.c +++ b/src/chat/chat_poll.c @@ -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; }