media: validate SQLite records before local block announcements

This commit is contained in:
evgeny committed 2026-10-24 17:26:11 +02:00
1 parent 1a212489fd
commit 81fc4291db
2 files changed
+45 -16

No files matched your search

+41 -15
View File
@@ -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);
}
+4 -1
View File
@@ -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);
// ── Отмена работы канала ──