From f6cc3b6e5353b9649008ec42892f0a54fceb64b7 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 6 Oct 2026 20:07:44 +0200 Subject: [PATCH] Make feedback roots depend on signed state and validate rebuilt keys --- src/chat/chat_feedback.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/chat/chat_feedback.c b/src/chat/chat_feedback.c index 37182e74..7c236fc3 100644 --- a/src/chat/chat_feedback.c +++ b/src/chat/chat_feedback.c @@ -133,9 +133,6 @@ static int fb_author_key(struct UTUN_INSTANCE* inst, const char* channel, uint64 int chat_feedback_ensure_channel(struct UTUN_INSTANCE* inst, const char* channel) { char table[40], sql[1024]; uint64_t id; if (fb_table(inst, channel, table, &id) < 0) return -1; - if (topo_node_sqlite_channel_get(inst->topo_sqlite_db, channel, NULL, 0, NULL, NULL, NULL, NULL) < 0) { - DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "feedback: unknown channel=%s", channel); return -1; - } snprintf(sql, sizeof(sql), "CREATE TABLE IF NOT EXISTS %s(" "record_key BLOB PRIMARY KEY CHECK(length(record_key)=32)," "bucket INTEGER NOT NULL,message_id BLOB NOT NULL CHECK(length(message_id)=32)," @@ -189,6 +186,8 @@ static int fb_apply_row(struct UTUN_INSTANCE* inst, const char* channel, const s DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "feedback: invalid signature ch=%s node=%016llx kind=%u ver=%llu", channel, (unsigned long long)r->node_id, r->kind, (unsigned long long)r->ver); return -1; } + char message_hex[65]; + for (unsigned i = 0; i < 32; i++) snprintf(message_hex + 2 * i, 3, "%02x", r->message_id[i]); struct chat_feedback_record old; int found = chat_feedback_get(inst, channel, r->message_id, r->node_id, r->kind, &old); if (found < 0) return -1; @@ -217,8 +216,8 @@ static int fb_apply_row(struct UTUN_INSTANCE* inst, const char* channel, const s int rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) return fb_db_error(inst, channel, "put step"); if (merkle_sync_recompute_path(inst, MT_DATA_FEEDBACK, channel, fb_read64(record_key.bytes)) < 0) return -1; - DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "feedback: applied ch=%s node=%016llx kind=%u ver=%llu previous=%llu bytes=%u", - channel, (unsigned long long)r->node_id, r->kind, (unsigned long long)r->ver, + DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "feedback: applied ch=%s message=%s node=%016llx kind=%u ver=%llu previous=%llu bytes=%u", + channel, message_hex, (unsigned long long)r->node_id, r->kind, (unsigned long long)r->ver, (unsigned long long)(found ? old.ver : 0), r->data_len); return 1; } @@ -313,7 +312,7 @@ static int fb_page(void* ctx, const char* channel, uint64_t prefix, int av, cons sqlite3_finalize(st); data[0] = count >> 8; data[1] = count & 255; *len = off; return result; } -/* Хеширует подписанные записи листа в том же порядке, что и страничный обход. */ +/* Хеширует подписанные тела: разные валидные подписи одного тела не меняют корень. */ static int fb_hash(void* ctx, const char* channel, uint8_t level, uint64_t prefix, EVP_MD_CTX* hash) { struct UTUN_INSTANCE* inst = ctx; char table[40], sql[256]; if (fb_table(inst, channel, table, NULL) < 0 || level != MT_MAX_LEVEL) { @@ -328,7 +327,7 @@ static int fb_hash(void* ctx, const char* channel, uint8_t level, uint64_t prefi struct chat_feedback_record r; uint8_t wire[FB_WIRE_MAX]; if (fb_read_row(st, &r) < 0) { count = -1; break; } int bytes = fb_encode(&r, wire); - if (bytes < 0 || EVP_DigestUpdate(hash, wire, bytes) != 1) { + if (bytes < 0 || EVP_DigestUpdate(hash, wire, bytes - 64) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "feedback: leaf digest failed ch=%s", channel); count = -1; break; } count++; @@ -397,7 +396,7 @@ int chat_feedback_init(struct UTUN_INSTANCE* inst) { char channel[21]; snprintf(channel, sizeof(channel), "%s", sqlite3_column_text(channels, 0)); char table[40], sql[256]; if (chat_feedback_ensure_channel(inst, channel) < 0 || fb_table(inst, channel, table, NULL) < 0) { result = -1; break; } - snprintf(sql, sizeof(sql), "SELECT message_id,node_id,kind,ver,data,signature FROM %s", table); + snprintf(sql, sizeof(sql), "SELECT message_id,node_id,kind,ver,data,signature,record_key,bucket FROM %s", table); sqlite3_stmt* st = NULL; if (sqlite3_prepare_v2(inst->topo_sqlite_db, sql, -1, &st, NULL) != SQLITE_OK) { result = fb_db_error(inst, channel, "init records"); break; @@ -413,6 +412,11 @@ int chat_feedback_init(struct UTUN_INSTANCE* inst) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "feedback: rebuild failed ch=%s node=%016llx", channel, (unsigned long long)r.node_id); result = -1; break; } + if (sqlite3_column_bytes(st, 6) != 32 || memcmp(record_key.bytes, sqlite3_column_blob(st, 6), 32) + || (uint64_t)sqlite3_column_int64(st, 7) != (fb_read64(record_key.bytes) >> 39)) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "feedback: corrupt local index ch=%s node=%016llx", + channel, (unsigned long long)r.node_id); result = -1; break; + } records++; } sqlite3_finalize(st);