From 81fc4291db354951dd18aaaed36ea6ba3526174d Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 24 Oct 2026 15:55:33 +0200 Subject: [PATCH] media: validate SQLite records before local block announcements --- src/media_delivery/media_delivery.c | 56 +++++++++++++++++++++-------- src/media_delivery/media_delivery.h | 5 ++- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/media_delivery/media_delivery.c b/src/media_delivery/media_delivery.c index b752c9c9..3b195500 100644 --- a/src/media_delivery/media_delivery.c +++ b/src/media_delivery/media_delivery.c @@ -1617,27 +1617,53 @@ void media_delivery_announce_local_blocks(struct UTUN_INSTANCE* inst) { media_delivery_refresh_roles(inst); sqlite3_stmt* st = NULL; - if (sqlite3_prepare_v2(db, "SELECT media_id, block_id, chunk, chat_id FROM media_files WHERE node_id=?", + if (sqlite3_prepare_v2(db, "SELECT id,media_id,block_id,chunk,chat_id FROM media_files WHERE node_id=?", -1, &st, NULL) != SQLITE_OK) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "%s: announce_local_blocks prepare failed: %s", MD_ID, sqlite3_errmsg(db)); return; } - sqlite3_bind_int64(st, 1, (sqlite3_int64)inst->node_id); + int rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)inst->node_id); - int announced = 0; - while (sqlite3_step(st) == SQLITE_ROW) { - const uint8_t* media_id = (const uint8_t*)sqlite3_column_blob(st, 0); - const uint8_t* block_id = (const uint8_t*)sqlite3_column_blob(st, 1); - uint32_t chunk = (uint32_t)sqlite3_column_int(st, 2); - const char* chat_id = (const char*)sqlite3_column_text(st, 3); - if (!media_id || !block_id || !chat_id || !chat_id[0]) continue; - uint64_t gid = strtoull(chat_id, NULL, 10); - if (gid == 0) continue; + uint64_t rows = 0, eligible = 0, skipped = 0; + if (rc == SQLITE_OK) while ((rc = sqlite3_step(st)) == SQLITE_ROW) { + rows++; + sqlite3_int64 row = sqlite3_column_int64(st, 0), chunk = sqlite3_column_int64(st, 3); + int media_type = sqlite3_column_type(st, 1), block_type = sqlite3_column_type(st, 2); + int chunk_type = sqlite3_column_type(st, 3), chat_type = sqlite3_column_type(st, 4); + int media_bytes = sqlite3_column_bytes(st, 1), block_bytes = sqlite3_column_bytes(st, 2); + int chat_bytes = sqlite3_column_bytes(st, 4); + const uint8_t* media = media_type == SQLITE_BLOB && media_bytes == 16 ? sqlite3_column_blob(st, 1) : NULL; + const uint8_t* block = block_type == SQLITE_BLOB && block_bytes == 16 ? sqlite3_column_blob(st, 2) : NULL; + const char* chat = chat_type == SQLITE_TEXT ? (const char*)sqlite3_column_text(st, 4) : NULL; + uint64_t gid = 0; + int valid_chat = chat && chat_bytes > 0 && chat_bytes <= 20 && chat[0] != '0'; + for (int i = 0; valid_chat && i < chat_bytes; i++) { + unsigned digit = (unsigned char)chat[i] - '0'; + if (digit > 9 || gid > (UINT64_MAX - digit) / 10) valid_chat = 0; + else gid = gid * 10 + digit; + } + const char* invalid = !media ? "media_id" : !block ? "block_id" + : chunk_type != SQLITE_INTEGER || chunk < 0 || chunk > UINT32_MAX ? "chunk" : !valid_chat ? "chat_id" : NULL; + if (invalid) { + skipped++; + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, + "%s: invalid local block row=%lld field=%s media_type=%d media_bytes=%d block_type=%d block_bytes=%d " + "chunk_type=%d chunk=%lld chat_type=%d chat_bytes=%d", MD_ID, (long long)row, invalid, + media_type, media_bytes, block_type, block_bytes, chunk_type, (long long)chunk, chat_type, chat_bytes); + continue; + } + uint8_t media_id[16], block_id[16]; + memcpy(media_id, media, sizeof(media_id)); memcpy(block_id, block, sizeof(block_id)); uint64_t supers[10]; int nsup = md_collect_supernodes(inst, gid, supers); for (int s = 0; s < nsup; s++) - media_delivery_send_have_block(inst, gid, supers[s], media_id, block_id, chunk); - announced++; + media_delivery_send_have_block(inst, gid, supers[s], media_id, block_id, (uint32_t)chunk); + eligible++; } - sqlite3_finalize(st); - DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "%s: announce_local_blocks done, blocks=%d", MD_ID, announced); + if (md_sql_finish(db, st, rc, "enumerate local blocks")) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "%s: local block scan incomplete rows=%llu eligible=%llu skipped=%llu", + MD_ID, (unsigned long long)rows, (unsigned long long)eligible, (unsigned long long)skipped); + return; + } + DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "%s: local block scan complete rows=%llu eligible=%llu skipped=%llu", + MD_ID, (unsigned long long)rows, (unsigned long long)eligible, (unsigned long long)skipped); } diff --git a/src/media_delivery/media_delivery.h b/src/media_delivery/media_delivery.h index fd6f5099..732b40ed 100644 --- a/src/media_delivery/media_delivery.h +++ b/src/media_delivery/media_delivery.h @@ -154,7 +154,10 @@ void media_delivery_send_have_block(struct UTUN_INSTANCE* inst, uint64_t group_i /* Переанонсировать суперузлам ВСЕ локальные блоки (media_files WHERE node_id=self). * Вызывается при старте после синхронизации, чтобы суперузел восстановил свой - * block_availability для уже скачанных ранее медиа. Идемпотентно на суперузле. */ + * block_availability для уже скачанных ранее медиа. Идемпотентно на суперузле. + * Некорректные SQL-типы/длины UUID, chunk вне uint32 и неканонический ID канала пропускаются с WARN. + * Ошибка SQL завершает обход с ERROR; уже отправленные корректные анонсы сохраняются. + * Анонсы best-effort, void не подтверждает доставку; UUID копируются до вложенных SQL/отправок. */ void media_delivery_announce_local_blocks(struct UTUN_INSTANCE* inst); // ── Отмена работы канала ──