From a9618169ab938e8a8b363cd8cc5f43c423d10994 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 07:04:28 +0100 Subject: [PATCH] fix(chat): load complete verified owner snapshots and validate editor input --- src/chat/chat_core.c | 82 +++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/src/chat/chat_core.c b/src/chat/chat_core.c index 61e60309..19fcef3b 100644 --- a/src/chat/chat_core.c +++ b/src/chat/chat_core.c @@ -642,6 +642,10 @@ struct chat_member_tags_key { struct chat_member_tags { char ch_id[64]; uint64_t node_id; + sqlite3* db; /* Заимствована до остановки сервиса. */ + char baseline[MS_ADM_TAGS_MAX + 1]; /* Проверенный owner-блок на момент загрузки/commit. */ + uint8_t signature[64], x25519[32], ed25519[32]; + int version, parsing_edit; int key_count; struct chat_member_tags_key keys[CHAT_MEMBER_TAGS_MAX_KEYS]; }; @@ -667,9 +671,12 @@ static int tags_is_append_key(const char* key) { static int tags_json_cb(const char* key, const char* json_val, void* arg) { struct chat_member_tags* t = (struct chat_member_tags*)arg; - if (t->key_count >= CHAT_MEMBER_TAGS_MAX_KEYS || strlen(key) >= CHAT_MEMBER_TAGS_KEY_SZ) { + if (!key[0] || t->key_count >= CHAT_MEMBER_TAGS_MAX_KEYS || strlen(key) >= CHAT_MEMBER_TAGS_KEY_SZ) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags JSON key limit exceeded", CC_ID); return -1; } + if (t->parsing_edit && tags_is_append_key(key) && strcmp(json_val, "yes") && strcmp(json_val, "no")) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: edited role must be yes/no key=%s", key); return -1; + } struct chat_member_tags_key* k = &t->keys[t->key_count]; snprintf(k->key, CHAT_MEMBER_TAGS_KEY_SZ, "%s", key); k->val = u_strdup(tags_is_append_key(key) ? tags_append_effective(json_val, NULL) : json_val); @@ -683,33 +690,64 @@ static int tags_json_cb(const char* key, const char* json_val, void* arg) { } struct chat_member_tags* chat_member_tags_load(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id) { - struct chat_core_ctx* cc = CC(inst); - if (!cc || !cc->initialized || !ch_id || !cc->db) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_load — not initialized", CC_ID); return NULL; } + struct chat_core_ctx* cc = inst ? CC(inst) : NULL; + if (!cc || !cc->initialized || !chat_channel_id(ch_id) || !node_id || !cc->db) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: invalid load context/channel/member"); return NULL; + } struct chat_member_tags* t = u_calloc(1, sizeof(*t)); if (!t) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_load — OOM", CC_ID); return NULL; } snprintf(t->ch_id, sizeof(t->ch_id), "%s", ch_id); - t->node_id = node_id; + t->node_id = node_id; t->db = cc->db; char peers_tbl[80]; peers_table_name(ch_id, peers_tbl, sizeof(peers_tbl)); sqlite3_stmt* st = NULL; - char sql[256]; snprintf(sql, sizeof(sql), "SELECT adm_tags FROM \"%s\" WHERE node_id=?", peers_tbl); - if (sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL) == SQLITE_OK) { - sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id); - if (sqlite3_step(st) == SQLITE_ROW) { - const char* tags = (const char*)sqlite3_column_text(st, 0); - if (tags && tags[0] && chat_member_tags_replace(t, tags) < 0) { - sqlite3_finalize(st); chat_member_tags_free(t); return NULL; - } - } - sqlite3_finalize(st); + char sql[256]; snprintf(sql, sizeof(sql), "SELECT adm_tags,adm_tags_sig,x25519_pubkey,ed25519_pubkey,source FROM \"%s\" WHERE node_id=?", peers_tbl); + const char* stage = "prepare"; int rc = sqlite3_prepare_v2(cc->db, sql, -1, &st, NULL); + if (rc == SQLITE_OK) { stage = "bind"; rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)node_id); } + if (rc == SQLITE_OK) { stage = "step"; rc = sqlite3_step(st); } + if (rc != SQLITE_ROW) goto error; + stage = "fields"; rc = SQLITE_CORRUPT; + int type = sqlite3_column_type(st, 0), bytes = sqlite3_column_bytes(st, 0), sig_type = sqlite3_column_type(st, 1); + const char* text = (const char*)sqlite3_column_text(st, 0); + const void *signature = sqlite3_column_blob(st, 1), *x = sqlite3_column_blob(st, 2), *ed = sqlite3_column_blob(st, 3); + if ((type != SQLITE_NULL && type != SQLITE_TEXT) || (type == SQLITE_TEXT && (!text || strlen(text) != (size_t)bytes)) || + bytes > MS_ADM_TAGS_MAX || sqlite3_column_type(st, 2) != SQLITE_BLOB || sqlite3_column_bytes(st, 2) != 32 || !x || + sqlite3_column_type(st, 3) != SQLITE_BLOB || sqlite3_column_bytes(st, 3) != 32 || !ed || sqlite3_column_int(st, 4)) goto error; + if (bytes) memcpy(t->baseline, text, (size_t)bytes + 1); + if (sig_type != SQLITE_NULL) { + if (sig_type != SQLITE_BLOB || sqlite3_column_bytes(st, 1) != 64 || !signature) goto error; + memcpy(t->signature, signature, 64); } - DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_load ch=%s nid=0x%016llx keys=%d", - CC_ID, ch_id, (unsigned long long)node_id, t->key_count); + memcpy(t->x25519, x, 32); memcpy(t->ed25519, ed, 32); + stage = "finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error; + t->version = member_sync_owner_version(cc->db, t->baseline); + if (t->version < 0) goto invalid; + if (t->version) { + uint8_t key[32], body[MS_ADM_TAGS_MAX + 8]; + int length = member_sync_build_owner_msg(node_id, t->baseline, body, sizeof(body)); + if (length < 0 || topo_node_sqlite_channel_get(cc->db, ch_id, NULL, 0, NULL, NULL, key, NULL) || + sc_ed25519_verify(key, body, (size_t)length, t->signature) != SC_OK) goto invalid; + if (chat_json_strings(cc->db, t->baseline, MS_ADM_TAGS_MAX, tags_json_cb, t)) goto invalid; + } else if (memcmp(t->signature, (uint8_t[64]){0}, 64)) goto invalid; + DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "tags: loaded ch=%s node=%016llx version=%d keys=%d", + ch_id, (unsigned long long)node_id, t->version, t->key_count); return t; +invalid: + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: invalid signed owner block ch=%s node=%016llx", ch_id, (unsigned long long)node_id); + chat_member_tags_free(t); return NULL; +error: + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: load ch=%s node=%016llx stage=%s rc=%d: %s", + ch_id, (unsigned long long)node_id, stage, rc, sqlite3_errmsg(cc->db)); + rc = sqlite3_finalize(st); + if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: load cleanup finalize=%d", rc); + chat_member_tags_free(t); return NULL; } int chat_member_tags_set(struct chat_member_tags* t, const char* key, const char* val) { - if (!t || !key) return -1; + if (!t || !key || !key[0] || !strcmp(key, "ver") || + (val && tags_is_append_key(key) && strcmp(val, "yes") && strcmp(val, "no"))) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "tags: invalid edit key/role or reserved version"); return -1; + } if (strlen(key) >= CHAT_MEMBER_TAGS_KEY_SZ) { DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_set — key too long", CC_ID); return -1; } if (val && strlen(val) > MS_ADM_TAGS_MAX) { DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_set — val too long", CC_ID); return -1; } @@ -722,7 +760,7 @@ int chat_member_tags_set(struct chat_member_tags* t, const char* key, const char if (!copy) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_set allocation failed", CC_ID); return -1; } if (idx >= 0) { u_free(t->keys[idx].val); t->keys[idx].val = copy; - } else if (t->key_count < CHAT_MEMBER_TAGS_MAX_KEYS) { + } else if (t->key_count < CHAT_MEMBER_TAGS_MAX_KEYS - (tags_find(t, "ver") < 0)) { struct chat_member_tags_key* k = &t->keys[t->key_count++]; snprintf(k->key, CHAT_MEMBER_TAGS_KEY_SZ, "%s", key); k->val = copy; @@ -743,8 +781,12 @@ int chat_member_tags_replace(struct chat_member_tags* t, const char* json) { if (!t || !json || strlen(json) > MS_ADM_TAGS_MAX) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_replace invalid input or size", CC_ID); return -1; } - struct chat_member_tags parsed = {0}; - int rc = json_flat_parse(json, tags_json_cb, &parsed); + struct chat_member_tags parsed = {.db = t->db, .parsing_edit = 1}; + int rc = chat_json_strings(t->db, json, MS_ADM_TAGS_MAX, tags_json_cb, &parsed); + char version[16]; snprintf(version, sizeof(version), "%d", t->version); + const char* requested = chat_member_tags_get(&parsed, "ver"); + if (requested && strcmp(requested, version)) rc = -1; + if (!requested && parsed.key_count == CHAT_MEMBER_TAGS_MAX_KEYS) rc = -1; if (rc != 0) { for (int i = 0; i < parsed.key_count; i++) { u_free(parsed.keys[i].raw); u_free(parsed.keys[i].val); } DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: tags_replace — invalid JSON", CC_ID); return -1;