From e0598ccc2265b4b2c5ab389cb85ae034b41c4806 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 05:08:31 +0100 Subject: [PATCH] fix(chat): commit avatar updates before notification and validate SQL reads --- src/chat/group_avatar.c | 247 ++++++++++++++++++--------- src/routing_layer/topo_node_sqlite.c | 67 +++++--- src/routing_layer/topo_node_sqlite.h | 6 +- 3 files changed, 212 insertions(+), 108 deletions(-) diff --git a/src/chat/group_avatar.c b/src/chat/group_avatar.c index 066ef621..2e5d6ee0 100644 --- a/src/chat/group_avatar.c +++ b/src/chat/group_avatar.c @@ -12,6 +12,7 @@ #include "../../lib/mem.h" #include "../../lib/u_async.h" #include +#include #define GA_STATE 1 #define GA_GET 2 @@ -51,7 +52,8 @@ static int ga_message(uint64_t group, const struct group_avatar_record* record, } static int ga_verify(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* r) { - if (!inst || !CC(inst) || !group || !r->version || r->version > INT64_MAX || r->size > GROUP_AVATAR_MAX) { + if (!inst || !CC(inst) || !CC(inst)->db || !group || group > INT64_MAX || !r || + !r->version || r->version > INT64_MAX || r->size > GROUP_AVATAR_MAX) { DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: invalid record group=%llu", (unsigned long long)group); return -1; } uint8_t hash[32], pub[32], bytes[GROUP_AVATAR_MAX + 64]; char ch[32]; ga_channel(group, ch); @@ -72,31 +74,40 @@ static int ga_verify(struct UTUN_INSTANCE* inst, uint64_t group, const struct gr } int group_avatar_get(struct UTUN_INSTANCE* inst, uint64_t group, struct group_avatar_record* r) { - if (!inst || !CC(inst) || !CC(inst)->db || !r) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: invalid read"); return -1; } - memset(r, 0, sizeof(*r)); char ch[32]; ga_channel(group, ch); sqlite3_stmt* st = NULL; - const char* sql = "SELECT avatar_version,avatar_data,avatar_hash,avatar_signature FROM channels WHERE channel_id=?"; - int rc = -1; - if (sqlite3_prepare_v2(CC(inst)->db, sql, -1, &st, NULL) == SQLITE_OK) { - sqlite3_bind_text(st, 1, ch, -1, SQLITE_STATIC); - if (sqlite3_step(st) == SQLITE_ROW) { - r->version = (uint64_t)sqlite3_column_int64(st, 0); int size = sqlite3_column_bytes(st, 1); - if ((!r->version && !size && !sqlite3_column_bytes(st, 2) && !sqlite3_column_bytes(st, 3)) || - (r->version && r->version <= INT64_MAX && size <= GROUP_AVATAR_MAX && - sqlite3_column_bytes(st, 2) == 32 && sqlite3_column_bytes(st, 3) == 64)) { - r->size = (uint16_t)size; - if (r->version) { - if (size) memcpy(r->data, sqlite3_column_blob(st, 1), size); - memcpy(r->hash, sqlite3_column_blob(st, 2), 32); memcpy(r->signature, sqlite3_column_blob(st, 3), 64); - } - rc = 0; - } - } - sqlite3_finalize(st); + if (!inst || !CC(inst) || !CC(inst)->db || !group || group > INT64_MAX || !r) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: invalid read group=%llu", (unsigned long long)group); return -1; } - if (!rc && r->version) rc = ga_verify(inst, group, r); - if (rc) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: stored record unavailable/invalid group=%llu: %s", - (unsigned long long)group, sqlite3_errmsg(CC(inst)->db)); - return rc; + struct group_avatar_record result = {0}; char ch[32]; ga_channel(group, ch); sqlite3_stmt* st = NULL; + const char* sql = "SELECT avatar_version,avatar_data,avatar_hash,avatar_signature FROM channels WHERE channel_id=?"; + sqlite3* db = CC(inst)->db; const char* stage = "prepare"; + int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL); + if (rc != SQLITE_OK) goto done; + stage = "bind"; rc = sqlite3_bind_text(st, 1, ch, -1, SQLITE_STATIC); if (rc != SQLITE_OK) goto done; + stage = "step"; rc = sqlite3_step(st); if (rc != SQLITE_ROW) goto done; + result.version = (uint64_t)sqlite3_column_int64(st, 0); int size = sqlite3_column_bytes(st, 1); + stage = "record fields"; rc = SQLITE_CORRUPT; + if (sqlite3_column_type(st, 0) != SQLITE_INTEGER || result.version > INT64_MAX || size > GROUP_AVATAR_MAX) goto done; + if (!result.version) { + if (size || sqlite3_column_bytes(st, 2) || sqlite3_column_bytes(st, 3)) goto done; + } else { + if (sqlite3_column_type(st, 1) != SQLITE_BLOB || sqlite3_column_type(st, 2) != SQLITE_BLOB || + sqlite3_column_type(st, 3) != SQLITE_BLOB || sqlite3_column_bytes(st, 2) != 32 || sqlite3_column_bytes(st, 3) != 64) goto done; + const void *data = sqlite3_column_blob(st, 1), *hash = sqlite3_column_blob(st, 2), *signature = sqlite3_column_blob(st, 3); + if ((size && !data) || !hash || !signature) { rc = SQLITE_NOMEM; goto done; } + result.size = (uint16_t)size; if (size) memcpy(result.data, data, (size_t)size); + memcpy(result.hash, hash, 32); memcpy(result.signature, signature, 64); + } + rc = SQLITE_OK; +done: + { + int final = sqlite3_finalize(st); + if (rc != SQLITE_OK || final != SQLITE_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: read group=%llu stage=%s rc=%d finalize=%d: %s", + (unsigned long long)group, stage, rc, final, sqlite3_errmsg(db)); return -1; + } + } + if (result.version && ga_verify(inst, group, &result) < 0) return -1; + *r = result; return 0; } static int ga_compare(uint64_t a, const uint8_t ah[32], uint64_t b, const uint8_t bh[32]) { @@ -112,59 +123,126 @@ static void ga_changed(struct UTUN_INSTANCE* inst, uint64_t group) { memcpy(event + 1, ch, event[0]); chat_event_post(inst, CHAT_EVT_CHANNEL_UPDATED, event, event[0] + 1); } -int group_avatar_apply(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* r) { +/* Блокировка охватывает проверку допуска/версии и запись; callback здесь не вызывается. */ +static int ga_begin(struct UTUN_INSTANCE* inst, uint64_t group) { + if (!inst || !CC(inst) || !CC(inst)->initialized || !CC(inst)->db || !group || group > INT64_MAX) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: inactive core/invalid group=%llu", (unsigned long long)group); return -1; + } + sqlite3* db = CC(inst)->db; + if (!sqlite3_get_autocommit(db)) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: external transaction rejected group=%llu", (unsigned long long)group); return -1; + } + int rc = sqlite3_exec(db, "BEGIN IMMEDIATE /* group avatar */", NULL, NULL, NULL); + if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: begin group=%llu rc=%d: %s", + (unsigned long long)group, rc, sqlite3_errmsg(db)); + return rc == SQLITE_OK ? 0 : -1; +} + +static int ga_finish(struct UTUN_INSTANCE* inst, uint64_t group, int result) { + sqlite3* db = CC(inst)->db; int rc; + if (result > 0) { + rc = sqlite3_exec(db, "COMMIT /* group avatar */", NULL, NULL, NULL); + if (rc == SQLITE_OK) return result; + DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: commit group=%llu rc=%d: %s", + (unsigned long long)group, rc, sqlite3_errmsg(db)); result = -1; + } + if (!sqlite3_get_autocommit(db)) { + rc = sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL); + if (rc != SQLITE_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: rollback group=%llu rc=%d: %s", + (unsigned long long)group, rc, sqlite3_errmsg(db)); result = -1; + } + if (!sqlite3_get_autocommit(db)) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: transaction remains open group=%llu; requesting owner stop/DB close", + (unsigned long long)group); utun_instance_stop(inst); result = -1; + } + } + return result; +} + +static int ga_write(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* r) { + sqlite3* db = CC(inst)->db; sqlite3_stmt* st = NULL; char ch[32]; ga_channel(group, ch); + const char* sql = "UPDATE channels SET avatar_version=?,avatar_data=?,avatar_hash=?,avatar_signature=? WHERE channel_id=?"; + const char* stage = "prepare"; int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL); + if (rc == SQLITE_OK) { stage = "bind version"; rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)r->version); } + if (rc == SQLITE_OK) { stage = "bind data"; rc = sqlite3_bind_blob(st, 2, r->data, r->size, SQLITE_STATIC); } + if (rc == SQLITE_OK) { stage = "bind hash"; rc = sqlite3_bind_blob(st, 3, r->hash, 32, SQLITE_STATIC); } + if (rc == SQLITE_OK) { stage = "bind signature"; rc = sqlite3_bind_blob(st, 4, r->signature, 64, SQLITE_STATIC); } + if (rc == SQLITE_OK) { stage = "bind channel"; rc = sqlite3_bind_text(st, 5, ch, -1, SQLITE_STATIC); } + if (rc == SQLITE_OK) { stage = "step"; rc = sqlite3_step(st); } + if (rc == SQLITE_DONE && sqlite3_changes(db) != 1) { stage = "changes != 1"; rc = SQLITE_CONSTRAINT; } + int final = sqlite3_finalize(st); + if (rc != SQLITE_DONE || final != SQLITE_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: write group=%llu stage=%s rc=%d finalize=%d: %s", + (unsigned long long)group, stage, rc, final, sqlite3_errmsg(db)); return -1; + } + return 1; +} + +/* Приём завершает всю работу с ga_peer до публикации события, способного удалить группу. */ +static int ga_apply_transaction(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* r) { if (!r) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: missing candidate record"); return -1; } - if (ga_verify(inst, group, r) < 0) return -1; - struct group_avatar_record current; - if (group_avatar_get(inst, group, ¤t) < 0) return -1; + if (ga_begin(inst, group) < 0) return -1; + int result = -1; struct group_avatar_record current; + if (ga_verify(inst, group, r) < 0 || group_avatar_get(inst, group, ¤t) < 0) goto done; int newer = ga_compare(r->version, r->hash, current.version, current.hash); if (r->version == current.version && memcmp(r->hash, current.hash, 32)) DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: concurrent versions group=%llu version=%llu accept=%d", (unsigned long long)group, (unsigned long long)r->version, newer > 0); - if (newer <= 0) return 0; - sqlite3_stmt* st = NULL; char ch[32]; ga_channel(group, ch); int rc = -1; - const char* sql = "UPDATE channels SET avatar_version=?,avatar_data=?,avatar_hash=?,avatar_signature=? WHERE channel_id=?"; - if (sqlite3_prepare_v2(CC(inst)->db, sql, -1, &st, NULL) == SQLITE_OK) { - sqlite3_bind_int64(st, 1, (sqlite3_int64)r->version); sqlite3_bind_blob(st, 2, r->data, r->size, SQLITE_STATIC); - sqlite3_bind_blob(st, 3, r->hash, 32, SQLITE_STATIC); sqlite3_bind_blob(st, 4, r->signature, 64, SQLITE_STATIC); - sqlite3_bind_text(st, 5, ch, -1, SQLITE_STATIC); - if (sqlite3_step(st) == SQLITE_DONE && sqlite3_changes(CC(inst)->db) == 1) rc = 1; - sqlite3_finalize(st); - } - if (rc < 0) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: database update failed group=%llu: %s", - (unsigned long long)group, sqlite3_errmsg(CC(inst)->db)); - else { - DEBUG_INFO(DEBUG_CATEGORY_CHAT, "group avatar: saved group=%llu version=%llu bytes=%u", - (unsigned long long)group, (unsigned long long)r->version, r->size); ga_changed(inst, group); - } - return rc; + result = newer > 0 ? ga_write(inst, group, r) : 0; +done: + result = ga_finish(inst, group, result); + if (result > 0) DEBUG_INFO(DEBUG_CATEGORY_CHAT, "group avatar: saved group=%llu version=%llu bytes=%u committed=1", + (unsigned long long)group, (unsigned long long)r->version, r->size); + return result; +} + +int group_avatar_apply(struct UTUN_INSTANCE* inst, uint64_t group, const struct group_avatar_record* r) { + int result = ga_apply_transaction(inst, group, r); + if (result > 0) ga_changed(inst, group); + return result; } int group_avatar_set(struct UTUN_INSTANCE* inst, uint64_t group, const uint8_t* data, size_t size) { - if (!inst || !CC(inst) || !CC(inst)->initialized || !CC(inst)->db || size > GROUP_AVATAR_MAX || - (size && !data) || !member_files_member(inst, group, inst->node_id, 0)) { - DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: publication denied group=%llu bytes=%zu", (unsigned long long)group, size); return -1; - } - uint8_t key[32], bytes[GROUP_AVATAR_MAX + 64]; char ch[32]; ga_channel(group, ch); - if (topo_node_sqlite_channel_get_priv(CC(inst)->db, ch, key) < 0) { - DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: publication requires group key group=%llu", (unsigned long long)group); return -1; + if (size > GROUP_AVATAR_MAX || (size && !data)) { + DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: invalid publication group=%llu bytes=%zu", (unsigned long long)group, size); return -1; } + if (ga_begin(inst, group) < 0) return -1; + int result = -1; uint8_t key[32] = {0}, bytes[GROUP_AVATAR_MAX + 64]; char ch[32]; ga_channel(group, ch); struct group_avatar_record r; - if (group_avatar_get(inst, group, &r) < 0 || r.version == INT64_MAX) { - DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: cannot allocate version group=%llu", (unsigned long long)group); return -1; + if (!member_files_member(inst, group, inst->node_id, 0)) { + DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: publication requires active membership group=%llu", (unsigned long long)group); goto done; } + if (topo_node_sqlite_channel_get_priv(CC(inst)->db, ch, key) < 0) { + DEBUG_WARN(DEBUG_CATEGORY_CHAT, "group avatar: publication requires group key group=%llu", (unsigned long long)group); goto done; + } + if (group_avatar_get(inst, group, &r) < 0) goto done; uint8_t hash[32]; SHA256(size ? data : r.data, size, hash); - if (r.version && r.size == size && !memcmp(r.hash, hash, 32)) return 0; + if (r.version && r.size == size && !memcmp(r.hash, hash, 32)) { result = 0; goto done; } + if (r.version == INT64_MAX) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: version exhausted group=%llu", (unsigned long long)group); goto done; + } int64_t us = ntp_time_get_us(inst); uint64_t now = us > 0 ? (uint64_t)us / 1000 : 0; r.version = now > r.version ? now : r.version + 1; r.size = (uint16_t)size; memcpy(r.hash, hash, 32); if (size) memcpy(r.data, data, size); - int n = ga_message(group, &r, bytes); int signed_ok = sc_ed25519_sign(key, bytes, n, r.signature); memset(key, 0, sizeof(key)); - if (signed_ok != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: signing failed group=%llu", (unsigned long long)group); return -1; } - return group_avatar_apply(inst, group, &r) > 0 ? 0 : -1; + int n = ga_message(group, &r, bytes); + if (sc_ed25519_sign(key, bytes, (size_t)n, r.signature) != SC_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: signing failed group=%llu", (unsigned long long)group); goto done; + } + if (ga_verify(inst, group, &r) < 0) goto done; + result = ga_write(inst, group, &r); +done: + OPENSSL_cleanse(key, sizeof(key)); result = ga_finish(inst, group, result); + if (result > 0) { + DEBUG_INFO(DEBUG_CATEGORY_CHAT, "group avatar: saved group=%llu version=%llu bytes=%u committed=1", + (unsigned long long)group, (unsigned long long)r.version, r.size); ga_changed(inst, group); + } + return result < 0 ? -1 : 0; } void group_avatar_gui_set(void* arg) { struct group_avatar_gui_request* q = arg; + if (!q || !q->inst) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: invalid GUI request"); u_free(q); return; } int rc = group_avatar_set(q->inst, q->group, q->data, q->size); uint8_t result[17]; mf_put64(result, q->id); mf_put64(result + 8, q->group); result[16] = rc ? 1 : 0; chat_event_post(q->inst, CHAT_EVT_GROUP_AVATAR_RESULT, result, sizeof(result)); u_free(q); @@ -337,9 +415,10 @@ static void ga_receive(struct ETCP_CONN* conn, struct ll_entry* entry) { DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "group avatar: block received group=%llu peer=%016llx version=%llu offset=%u bytes=%u total=%u", (unsigned long long)group, (unsigned long long)peer, (unsigned long long)version, offset, block, size); if (p->rx_offset == size) { - int applied = group_avatar_apply(ctx->inst, group, p->rx); ga_release(p, 0); + int applied = ga_apply_transaction(ctx->inst, group, p->rx); ga_release(p, 0); p->requested_at = applied < 0 ? get_time_tb() : 0; p->state_pending = 1; ga_schedule(p); + if (applied > 0) ga_changed(ctx->inst, group); } goto out; malformed: @@ -386,29 +465,39 @@ static void ga_tick(void* arg) { static int ga_schema(sqlite3* db) { const char* names[] = {"avatar_version", "avatar_data", "avatar_hash", "avatar_signature"}; int present[4] = {0}; - sqlite3_stmt* st = NULL; - if (sqlite3_prepare_v2(db, "PRAGMA table_info(channels)", -1, &st, NULL) != SQLITE_OK) goto error; - while (sqlite3_step(st) == SQLITE_ROW) for (unsigned i = 0; i < 4; i++) - if (!strcmp((const char*)sqlite3_column_text(st, 1), names[i])) present[i] = 1; - sqlite3_finalize(st); - if (sqlite3_exec(db, "SAVEPOINT group_avatar_schema", NULL, NULL, NULL) != SQLITE_OK) goto error; + if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema database missing"); return -1; } + sqlite3_stmt* st = NULL; const char* stage = "savepoint"; int started = 0, added = 0; + int rc = sqlite3_exec(db, "SAVEPOINT group_avatar_schema", NULL, NULL, NULL); + if (rc != SQLITE_OK) goto error; + started = 1; stage = "table info prepare"; rc = sqlite3_prepare_v2(db, "PRAGMA table_info(channels)", -1, &st, NULL); + if (rc != SQLITE_OK) goto error; + stage = "table info step"; + while ((rc = sqlite3_step(st)) == SQLITE_ROW) { + const char* name = (const char*)sqlite3_column_text(st, 1); + if (!name) { rc = SQLITE_CORRUPT; goto error; } + for (unsigned i = 0; i < 4; i++) if (!strcmp(name, names[i])) present[i] = 1; + } + if (rc != SQLITE_DONE) goto error; + stage = "table info finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error; for (unsigned i = 0; i < 4; i++) if (!present[i]) { char sql[128]; snprintf(sql, sizeof(sql), "ALTER TABLE channels ADD COLUMN %s %s", names[i], i ? "BLOB" : "INTEGER NOT NULL DEFAULT 0"); - if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) { - DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: cannot add schema column=%s: %s", names[i], sqlite3_errmsg(db)); - if (sqlite3_exec(db, "ROLLBACK TO group_avatar_schema; RELEASE group_avatar_schema", NULL, NULL, NULL) != SQLITE_OK) - DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema rollback failed: %s", sqlite3_errmsg(db)); - return -1; - } - DEBUG_INFO(DEBUG_CATEGORY_CHAT, "group avatar: added schema column=%s", names[i]); + stage = names[i]; rc = sqlite3_exec(db, sql, NULL, NULL, NULL); if (rc != SQLITE_OK) goto error; + added++; } - if (sqlite3_exec(db, "RELEASE group_avatar_schema", NULL, NULL, NULL) == SQLITE_OK) return 0; - DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema commit failed: %s", sqlite3_errmsg(db)); - if (sqlite3_exec(db, "ROLLBACK TO group_avatar_schema; RELEASE group_avatar_schema", NULL, NULL, NULL) != SQLITE_OK) - DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema rollback after commit error failed: %s", sqlite3_errmsg(db)); - return -1; + stage = "release"; rc = sqlite3_exec(db, "RELEASE group_avatar_schema", NULL, NULL, NULL); if (rc != SQLITE_OK) goto error; + if (added) DEBUG_INFO(DEBUG_CATEGORY_CHAT, "group avatar: schema ready added_columns=%d outer_transaction=%d", added, !sqlite3_get_autocommit(db)); + return 0; error: - DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema initialization failed: %s", sqlite3_errmsg(db)); return -1; + DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema initialization stage=%s rc=%d: %s", stage, rc, sqlite3_errmsg(db)); + rc = sqlite3_finalize(st); + if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema cleanup finalize rc=%d", rc); + if (started && !sqlite3_get_autocommit(db)) { + rc = sqlite3_exec(db, "ROLLBACK TO group_avatar_schema", NULL, NULL, NULL); + if (rc == SQLITE_OK) rc = sqlite3_exec(db, "RELEASE group_avatar_schema", NULL, NULL, NULL); + if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group avatar: schema rollback rc=%d;owner must rollback/close DB: %s", + rc, sqlite3_errmsg(db)); + } + return -1; } int group_avatar_init(struct UTUN_INSTANCE* inst) { diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index 628980e7..2e7ace4a 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -10,6 +10,7 @@ #include #include #include +#include #define PEERS_JOIN_SIG_SIZE 64 #define PEERS_JOIN_TS_SIZE 8 @@ -984,38 +985,50 @@ done: return result; } -int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id, - uint8_t* ed25519_priv_out) { - if (!db || !channel_id || !ed25519_priv_out) return -1; - sqlite3_stmt* stmt = NULL; - if (sqlite3_prepare_v2(db, - "SELECT ed25519_privkey FROM channels WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1; - sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC); - if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; } - const uint8_t* blob = (const uint8_t*)sqlite3_column_blob(stmt, 0); - if (!blob || sqlite3_column_bytes(stmt, 0) < 32) { sqlite3_finalize(stmt); return -1; } - memcpy(ed25519_priv_out, blob, 32); - sqlite3_finalize(stmt); - return 0; +/* Выходные ключи публикуются только после успешного завершения SELECT. */ +static int channel_private_keys(sqlite3* db, const char* channel, uint8_t* x25519, uint8_t* ed25519) { + if (!db || !channel || !ed25519) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: missing argument"); return -1; + } + const char* sql = x25519 ? "SELECT x25519_privkey, ed25519_privkey FROM channels WHERE channel_id=?" : + "SELECT ed25519_privkey FROM channels WHERE channel_id=?"; + sqlite3_stmt* st = NULL; uint8_t keys[64] = {0}; int columns = x25519 ? 2 : 1, result = -1; + const char* stage = "prepare"; int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL); + if (rc != SQLITE_OK) goto done; + stage = "bind"; rc = sqlite3_bind_text(st, 1, channel, -1, SQLITE_STATIC); if (rc != SQLITE_OK) goto done; + stage = "step"; rc = sqlite3_step(st); if (rc != SQLITE_ROW) goto done; + for (int i = 0; i < columns; i++) { + if (sqlite3_column_type(st, i) == SQLITE_NULL) { rc = SQLITE_DONE; goto done; } + stage = "key size/type"; rc = SQLITE_CORRUPT; + if (sqlite3_column_type(st, i) != SQLITE_BLOB || sqlite3_column_bytes(st, i) != 32) goto done; + const void* key = sqlite3_column_blob(st, i); if (!key) { rc = SQLITE_NOMEM; goto done; } + memcpy(keys + i * 32, key, 32); + } + rc = SQLITE_OK; +done: + { + int final = sqlite3_finalize(st); + if (rc == SQLITE_OK && final == SQLITE_OK) { + if (x25519) memcpy(x25519, keys, 32); + memcpy(ed25519, keys + (columns - 1) * 32, 32); result = 0; + } else if (rc == SQLITE_DONE && final == SQLITE_OK) { + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "channel private keys unavailable channel=%s", channel); + } else DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys channel=%s stage=%s rc=%d finalize=%d: %s", + channel, stage, rc, final, sqlite3_errmsg(db)); + } + OPENSSL_cleanse(keys, sizeof(keys)); return result; +} + +int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id, uint8_t* ed25519_priv_out) { + return channel_private_keys(db, channel_id, NULL, ed25519_priv_out); } int topo_node_sqlite_channel_get_privs(sqlite3* db, const char* channel_id, uint8_t* x25519_priv_out, uint8_t* ed25519_priv_out) { - if (!db || !channel_id || !x25519_priv_out || !ed25519_priv_out) return -1; - sqlite3_stmt* stmt = NULL; - if (sqlite3_prepare_v2(db, - "SELECT x25519_privkey, ed25519_privkey FROM channels WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1; - sqlite3_bind_text(stmt, 1, channel_id, -1, SQLITE_STATIC); - if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return -1; } - const uint8_t* x = (const uint8_t*)sqlite3_column_blob(stmt, 0); - const uint8_t* e = (const uint8_t*)sqlite3_column_blob(stmt, 1); - if (!x || sqlite3_column_bytes(stmt, 0) < 32 || !e || sqlite3_column_bytes(stmt, 1) < 32) { - sqlite3_finalize(stmt); return -1; + if (!x25519_priv_out) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: X25519 output missing"); return -1; } - memcpy(x25519_priv_out, x, 32); - memcpy(ed25519_priv_out, e, 32); - sqlite3_finalize(stmt); - return 0; + return channel_private_keys(db, channel_id, x25519_priv_out, ed25519_priv_out); } /* Импорт пары: savepoint охватывает UPDATE и его завершение, внешний commit остаётся вызывающему. */ diff --git a/src/routing_layer/topo_node_sqlite.h b/src/routing_layer/topo_node_sqlite.h index bf6ab18f..0841b4fd 100644 --- a/src/routing_layer/topo_node_sqlite.h +++ b/src/routing_layer/topo_node_sqlite.h @@ -122,11 +122,13 @@ int topo_node_sqlite_channel_get(sqlite3* db, const char* channel_id, char* name_out, int name_sz, uint64_t* owner_node_id, uint8_t* x25519_pub, uint8_t* ed25519_pub, uint8_t* signature); -/* Прочитать первые 32 байта Ed25519 privkey; 0/-1, отсутствующий/короткий ключ даёт -1. */ +/* Прочитать Ed25519 privkey ровно32 байта BLOB; 0/-1, отсутствие/повреждение/SQL-ошибка даёт -1. + * Проверяются bind/step/finalize; при отказе выход не меняется. */ int topo_node_sqlite_channel_get_priv(sqlite3* db, const char* channel_id, uint8_t* ed25519_priv_out); -/* Прочитать оба канальных privkey (x25519 + ed25519). Возвращает 0 если оба есть. */ +/* Прочитать оба канальных privkey (x25519 + ed25519), каждый ровно32 байта BLOB. + * 0 — оба прочитаны, -1 — отказ с сохранением обоих выходов. Временные ключи очищаются. */ int topo_node_sqlite_channel_get_privs(sqlite3* db, const char* channel_id, uint8_t* x25519_priv_out, uint8_t* ed25519_priv_out);