From 76527de44fe76832146bd0ca9efa2b77a4637f40 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 26 Oct 2026 19:30:30 +0100 Subject: [PATCH] Store admin keys atomically and reject invalid transfer context --- src/chat/chat_admin.c | 25 +++++++++++--- src/routing_layer/topo_node_sqlite.c | 51 +++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/chat/chat_admin.c b/src/chat/chat_admin.c index 5b862910..404875ae 100644 --- a/src/chat/chat_admin.c +++ b/src/chat/chat_admin.c @@ -45,12 +45,23 @@ static int ed25519_pub_from_priv(const uint8_t priv[32], uint8_t pub[32]) { static void admin_key_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { if (!entry) return; if (!conn || !entry->dgram || entry->len < ROUTER_SVC_PAYLOAD_OFF + 8 + 32 + 32) { + DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: recv — invalid delivery len=%u", CA_ID, entry->len); queue_dgram_free(entry); queue_entry_free(entry); return; } struct UTUN_INSTANCE* inst = conn->instance; - if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return; } + if (!inst) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: recv — no instance", CA_ID); + queue_dgram_free(entry); queue_entry_free(entry); return; + } struct chat_core_ctx* cc = CC(inst); - if (!cc || !cc->initialized || !cc->db) { queue_dgram_free(entry); queue_entry_free(entry); return; } + if (!cc || !cc->initialized || !cc->db) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: recv — not initialized", CA_ID); + queue_dgram_free(entry); queue_entry_free(entry); return; + } + if (!sqlite3_get_autocommit(cc->db)) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: recv — import rejected inside external transaction", CA_ID); + queue_dgram_free(entry); queue_entry_free(entry); return; + } const uint8_t* d = entry->dgram; uint64_t from_node; @@ -84,6 +95,10 @@ static void admin_key_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { if (topo_node_sqlite_channel_set_privs(cc->db, ch_id, x_priv, ed_priv) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: recv — set_privs FAILED ch=%s from 0x%016llx", CA_ID, ch_id, (unsigned long long)from_node); + if (!sqlite3_get_autocommit(cc->db)) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: recv — rollback left transaction open ch=%s; restart required", CA_ID, ch_id); + inst->running = 0; + } queue_dgram_free(entry); queue_entry_free(entry); return; } @@ -120,8 +135,8 @@ void chat_admin_destroy(struct UTUN_INSTANCE* inst) { /* ─── передача прав (вызывается из uasync-потока) ─── */ void chat_core_transfer_admin(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t target_node_id) { - struct chat_core_ctx* cc = CC(inst); - if (!cc || !cc->initialized || !inst || !cc->db) { + struct chat_core_ctx* cc = inst ? CC(inst) : NULL; + if (!cc || !cc->initialized || !cc->db) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: transfer — not initialized", CA_ID); return; } @@ -151,7 +166,7 @@ void chat_core_transfer_admin(struct UTUN_INSTANCE* inst, const char* ch_id, uin struct ll_entry* entry = queue_entry_new(0); if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: transfer — queue_entry_new failed", CA_ID); return; } entry->dgram = u_malloc(1 + sizeof(payload)); - if (!entry->dgram) { queue_entry_free(entry); return; } + if (!entry->dgram) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: transfer — payload allocation failed", CA_ID); queue_entry_free(entry); return; } entry->dgram[0] = ETCP_RT_ID_ADMIN_KEY; memcpy(entry->dgram + 1, payload, sizeof(payload)); entry->len = 1 + (uint16_t)sizeof(payload); diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index d5ec47d9..275a8887 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -953,18 +953,51 @@ int topo_node_sqlite_channel_get_privs(sqlite3* db, const char* channel_id, return 0; } +/* Импорт пары: savepoint охватывает UPDATE и его завершение, внешний commit остаётся вызывающему. */ int topo_node_sqlite_channel_set_privs(sqlite3* db, const char* channel_id, const uint8_t* x25519_priv, const uint8_t* ed25519_priv) { - if (!db || !channel_id || !x25519_priv || !ed25519_priv) return -1; + if (!db || !channel_id || !channel_id[0] || !x25519_priv || !ed25519_priv) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: invalid arguments"); return -1; + } + int own = sqlite3_get_autocommit(db); + int rc = sqlite3_exec(db, "SAVEPOINT channel_private_keys", NULL, NULL, NULL); + if (rc != SQLITE_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: channel=%s stage=savepoint rc=%d: %s", + channel_id, rc, sqlite3_errmsg(db)); return -1; + } sqlite3_stmt* stmt = NULL; - if (sqlite3_prepare_v2(db, - "UPDATE channels SET x25519_privkey=?, ed25519_privkey=? WHERE channel_id=?", -1, &stmt, NULL) != SQLITE_OK) return -1; - sqlite3_bind_blob(stmt, 1, x25519_priv, 32, SQLITE_STATIC); - sqlite3_bind_blob(stmt, 2, ed25519_priv, 32, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, channel_id, -1, SQLITE_STATIC); - int rc = sqlite3_step(stmt) == SQLITE_DONE ? 0 : -1; - sqlite3_finalize(stmt); - return rc; + const char* stage = "prepare"; + rc = sqlite3_prepare_v2(db, "UPDATE channels SET x25519_privkey=?, ed25519_privkey=? WHERE channel_id=?", -1, &stmt, NULL); + if (rc != SQLITE_OK) goto fail; + stage = "bind x25519"; rc = sqlite3_bind_blob(stmt, 1, x25519_priv, 32, SQLITE_STATIC); + if (rc != SQLITE_OK) goto fail; + stage = "bind ed25519"; rc = sqlite3_bind_blob(stmt, 2, ed25519_priv, 32, SQLITE_STATIC); + if (rc != SQLITE_OK) goto fail; + stage = "bind channel"; rc = sqlite3_bind_text(stmt, 3, channel_id, -1, SQLITE_STATIC); + if (rc != SQLITE_OK) goto fail; + stage = "update"; rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) goto fail; + int changed = sqlite3_changes(db); + stage = "finalize"; rc = sqlite3_finalize(stmt); stmt = NULL; + if (rc != SQLITE_OK) goto fail; + if (changed != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: channel=%s changed=%d expected=1", channel_id, changed); + stage = "row count"; rc = SQLITE_NOTFOUND; goto fail; + } + stage = "release"; rc = sqlite3_exec(db, "RELEASE channel_private_keys", NULL, NULL, NULL); + if (rc != SQLITE_OK) goto fail; + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "channel private keys stored: channel=%s committed=%d", channel_id, own); + return 0; +fail: + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: channel=%s stage=%s rc=%d: %s", + channel_id, stage, rc, sqlite3_errmsg(db)); + int final_rc = sqlite3_finalize(stmt); + if (final_rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_BGP, + "channel private keys: cleanup finalize channel=%s rc=%d: %s", channel_id, final_rc, sqlite3_errmsg(db)); + if (!sqlite3_get_autocommit(db) && + sqlite3_exec(db, "ROLLBACK TO channel_private_keys; RELEASE channel_private_keys", NULL, NULL, NULL) != SQLITE_OK) + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "channel private keys: rollback failed channel=%s: %s", channel_id, sqlite3_errmsg(db)); + return -1; } int topo_node_sqlite_node_set_online(sqlite3* db, uint64_t node_id, int online) {