diff --git a/doc/encrypted_attachments.md b/doc/encrypted_attachments.md index 5d26c828..9813d1f8 100644 --- a/doc/encrypted_attachments.md +++ b/doc/encrypted_attachments.md @@ -14,16 +14,23 @@ Android показывает зашифрованное вложение отд Список чатов сохраняет общее название зашифрованного вложения вместо manifest. Автор генерирует независимые случайные crypto_UUID и ключ AES-256 на каждую -подготовку. Штатно ключ записывается в локальную SQLite до шифрования. При аудите -26.10.2026 подтверждено исключение: отказ `sqlite3_bind_blob` ключа не проверяется, -и подготовка запускается со строкой `key=NULL`, несмотря на сообщение «key saved». -Контейнер создаётся, переданный исходник удаляется; последующая проверка -`chat_attachment_bind` запрещает публикацию, и отправка завершается событием ошибки. -Это открытый дефект сохранения ключа; аудит модуля остаётся частичным. -Стенд, точная граница проверки и план исправления — в -[chat_audit_findings.txt](chat_audit_findings.txt), продолжение chat_attachment. -Готовый контейнер -передаётся через обычные media_index/media_delivery. UUID транспортного медиа +подготовку. Ключ, UUID, автор, пути и метаданные записываются в локальную SQLite +собственной короткой транзакцией до передачи подготовленного файла worker. +Проверяются prepare, все bind, step, ровно одна добавленная строка, finalize и commit. +Внешняя транзакция отклоняется без изменений. До принятия ошибка возвращает `-1`: +файл остаётся у вызывающего, worker и событие отправки не создаются. +После commit возвращается `0`, файл принадлежит задаче и удаляется также при её отказе; +ненулевой request получает один `MSG_SUBMITTED`, в том числе синхронно при отказе запуска worker. +Исходный пользовательский файл сохраняется: общий frontend передаёт сюда собственную подготовленную копию. + +Неудачный rollback с оставшейся открытой транзакцией отключает модуль вложений и +запрашивает остановку ядра. Desktop и Android учитывают запрос в своих poll-циклах; +владелец освобождает ядро и закрывает SQLite после возврата из poll. Текущий poll может +доставить другие callbacks до выхода. Политика общей БД остаётся WAL/synchronous=NORMAL: +завершённый commit до worker не является гарантией сохранности после отключения питания. +История дефекта и проверок — в [chat_audit_findings.txt](chat_audit_findings.txt). + +Готовый контейнер передаётся через обычные media_index/media_delivery. UUID транспортного медиа и crypto_UUID имеют разные назначения: первый идентифицирует сетевые блоки, второй — контейнер и ключ. Повтор доставки передаёт прежний контейнер. @@ -154,6 +161,12 @@ backpressure и повтором через 5 секунд; restart продол `test_attachment_send` проверяет восемь отказов MIME до ключей/worker/файлов, затем останавливает сервис после настоящего crypto rename до completion. +`test_chat_attachment` проверяет 13 SQL-отказов с сохранением source/прежних строк/файлов, +отсутствием worker и события до принятия, затем настоящую публикацию при каждом повторе. +Отдельное readonly-соединение проверяет точные ключ, автора, UUID, метаданные и пути до worker. +Дополнительно проверяются настоящий commit veto, INSERT ABORT/IGNORE, внешняя транзакция, +синхронный отказ запуска, restart с опубликованным ключом и отказ rollback с закрытием БД владельцем. + `test_encrypted_attachment`: реальные A–S–B/C, скачивание с S при выключенном A, перезапуски автора с запросами и получателя с ключом, выдача B и отказ C, отсутствие ключа на S/C, отрицательные control-пакеты, повтор GRANT, diff --git a/src/chat/chat_attachment.c b/src/chat/chat_attachment.c index d3674316..a25a2158 100644 --- a/src/chat/chat_attachment.c +++ b/src/chat/chat_attachment.c @@ -236,16 +236,19 @@ static int ak_store_preparation(struct ak_file* job, const uint8_t* info, int in DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "attachment keys: author preparation committed before encryption channel=%s", job->channel); return 0; fail: - DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: preparation channel=%s stage=%s rc=%d: %s", job->channel, stage, rc, sqlite3_errmsg(db)); + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: preparation channel=%s stage=%s rc=%d (%s) db=%s", + job->channel, stage, rc, sqlite3_errstr(rc), sqlite3_errmsg(db)); int final_rc = sqlite3_finalize(st); if (final_rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: preparation cleanup finalize channel=%s rc=%d", job->channel, final_rc); if (!sqlite3_get_autocommit(db)) { int rollback_rc = sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL); if (rollback_rc != SQLITE_OK) - DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: preparation rollback channel=%s rc=%d: %s", job->channel, rollback_rc, sqlite3_errmsg(db)); + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: preparation rollback channel=%s rc=%d (%s) db=%s", + job->channel, rollback_rc, sqlite3_errstr(rollback_rc), sqlite3_errmsg(db)); if (!sqlite3_get_autocommit(db)) { - DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: transaction remains open channel=%s; quiescing attachments and requesting core stop", job->channel); + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment keys: transaction remains open channel=%s; quiescing attachments and requesting core stop", + job->channel); chat_attachment_quiesce(inst); utun_instance_stop(inst); }