fix(chat): load complete verified owner snapshots and validate editor input

This commit is contained in:
evgeny committed 2026-10-27 07:04:28 +01:00
1 parent 16d7fc4fb5
commit a9618169ab
1 file changed
+62 -20
+62 -20
View File
@@ -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;