diff --git a/doc/media_async_audit_findings.txt b/doc/media_async_audit_findings.txt index 5725360d..af9e79a8 100644 --- a/doc/media_async_audit_findings.txt +++ b/doc/media_async_audit_findings.txt @@ -29,25 +29,130 @@ source/destination. Разные строки могут обозначать о - /tmp/media-encrypted-alias-lvVS77/ — сохранённые входы и результаты опыта. Проектные .c/.h для воспроизведения не менялись. -Рекомендуемое исправление +Архитектурно уточнённый фикс — проект, код ещё не изменён -1. У обеих функций явно закрепить контракт: source остаётся неизменным; - destination — отдельный временный файл вызывающего, который допустимо удалить при ошибке. - Файлы/каталоги не должны подменяться во время вызова; это тот же контракт стабильности, - что у существующих file_io-примитивов. -2. До открытия назначения с усечением проверить идентичность через общий file_io_paths. - Результат1 (один файл) здесь означает ошибку. Отдельно проверять успешное открытие - источника до создания назначения. Использовать общие UTF-8/regular-file примитивы, - не дублировать POSIX/Windows сравнение идентичности в encrypted_file. -3. Ранняя ошибка не должна изменять source, удалять его alias или менять существующее - назначение. Сообщение об отказе должно назвать операцию/пути и причину до усечения. -4. Сохранить нынешнее владение: chat_attachment/dm_media публикуют результат после work. - Внутри encrypted_file не нужен второй независимый слой публикации или новый async owner. -5. Добавить регрессии write/read: совпадающая строка, /./, hardlink, конечная ссылка; - при отказе проверять неизменность файлов и отсутствие удаления alias. Проверить - отсутствующий источник и размеры0/1/32768/32769, обычные roundtrip и отмену pipeline. -6. Прогнать профильные тесты, ASAN/UBSan и полный набор; затем продолжить аудит - остальных контрактов обоих каталогов. Формат контейнера и nonce менять не требуется. +Одной проверки file_io_paths перед fopen(wb) недостаточно для полного исправления +контракта владения. Сейчас encrypted_file удаляет destination при ошибке, а +ak_file_done повторно удаляет заранее вычисленный .part даже при раннем отказе. +Нужно убрать открытие произвольного назначения с усечением и очистку по одному имени. + +Граница модулей + +- lib/file_io: UTF-8/regular-file проверки, идентичность, эксклюзивный temp, + flush/sync/close, atomic rename, владение temp и защита от orphan cleanup. +- encrypted_file: создание/проверка контейнера в собственном temp; без публикации + конечного имени, SQLite и состояния async-задачи. +- chat_attachment: задача владеет temp, выбирает конечное имя и выполняет одну + публикацию; completion меняет состояние БД/UI и освобождает только свои ресурсы. + Подготовка, sync и rename остаются в worker, как сейчас; новые файловые I/O в uasync не добавляются. + +Предлагаемый контракт write/read + +1. Функция получает source, конечный target_path и указатель на обнулённый file_io_temp. + target_path задаёт место будущего результата; он не открывается с усечением. + До начала работы file_io_paths(source,target_path) обязан вернуть 0. + Совпадение строки, /./, hardlink и конечная ссылка назначения отклоняются без изменений. +2. Источник открывается общим file_io_open_read. Только после успешного открытия + создаётся уникальный temp рядом с target_path через file_io_temp_open. + На POSIX он имеет права 0600; создание эксклюзивное, владелец сразу учтён в общем реестре. +3. Контейнер пишется/проверяется через уже открытый FILE этого temp. Нет второго .part, + второго открытия выхода по пути или дополнительного слоя публикации внутри codec. +4. Полностью проверяются данные, закрытие входа и durable завершение выхода. + При ошибке удаляется только созданный этой операцией temp; существующие source/target сохраняются. + При успехе возвращается закрытый, синхронизированный temp, который ещё не опубликован. + Результат info/type у read присваивается только после полного успешного завершения подготовки. +5. Владелец file_io_temp размещается прямо в ak_file и имеет стабильный адрес до done; + копировать его структуру нельзя: реестр хранит адрес владельца. + Функции не принимают уже активный output и не уничтожают его при отказе аргументов. + +Единая политика durability + +В file_io_temp_open добавить flags, в file_io_temp хранить выбранный режим на весь +срок операции. FILE_IO_DURABLE включает обе части: finish и publish. +Обычные пользователи передают 0 и сохраняют свою семантику; отдельные флаги в каждом +finish/publish не нужны. Это предотвращает случайное сохранение файла без сохранения имени. + +Durable finish: fflush -> fsync/_commit того же открытого fd -> fclose. Ошибка любой +фазы сохраняет первый код ошибки; close выполняется и при отказе sync. Открывать путь +заново ради fsync не нужно. После отказа temp остаётся у владельца до discard. + +Publish повторно проверяет идентичность source/target и принимает только закрытый temp. +На POSIX: rename и fsync каталога назначения; соседний temp находится в том же каталоге. +На Windows: общий wide API и MOVEFILE_WRITE_THROUGH. Родительский каталог вычисляется +без обрезания: имя без '/' означает '.', '/file' означает '/'. +Политика sync принадлежит file_io, а не формату зашифрованного контейнера. + +Точка фиксации — успешный rename. До неё ошибка сохраняет старый target. +После rename ошибка fsync каталога означает: результат уже установлен, долговечность +имени не подтверждена. Нельзя обещать откат, удалять новый файл вслепую или восстанавливать +старое содержимое. В этот момент temp уже отсоединён от реестра и его имя в owner пусто. +Этот переход должен быть явно описан в file_io.h и проверен тестом. +Длительный fsync выполняется после снятия блокировки реестра временных файлов. + +Владение задачи и отмена + +ak_file хранит file_io_temp вместо temporary[...]. Worker после успешной подготовки +один раз вызывает file_io_temp_publish с тем же target и источником. +До publish поле temporary непустое; после успешного rename оно пустое даже при +ошибке directory sync. По этому переходу задача фиксирует факт своей публикации. +Completion всегда может вызвать discard: после rename он ничего не удаляет. +Cleanup при отмене/удалении канала учитывает отдельно действительно созданные/ +заменённые артефакты. Вычисленный путь не является доказательством владения файлом. +При отказе до создания выхода нельзя удалять существующий target или его alias. + +Отдельных async owners, refcount и фоновых потоков не требуется. Ключ по-прежнему сохраняется +до шифрования; ready получателя/публикация сообщения подтверждаются только после +успешного worker и проверки актуальности канала в completion. Авторская строка ключа +сохраняется до worker по прежним правилам. Общей транзакции ФС/SQLite нет: после rename +путь остаётся известен attachment_keys для последующей очистки/повторной операции. + +Кеш и восстановление после crash + +Активный temp защищён существующим file_io-реестром, в том числе после finish. +В chat_msg убрать специальное сохранение вычисленного cipher_path.part; конечный +cipher_path по-прежнему защищается attachment_keys, временный файл — своим owner. +После crash реестра нет: такие temp допустимо удалить. + +Нельзя ограничиться нынешним orphan walk дерева media: plaintext-temp находится +в decrypted, а ak_recover_preparations знает только фиксированный .part. +В init/очистке после завершения workers нужно удалять оставшиеся временные файлы +строгого собственного формата .uenc.tmp.<32hex> / .file.tmp.<32hex> +в управляемых каталогах media/decrypted. Использовать file_io_remove_orphan, +чтобы сохранить активные temp другого instance того же процесса. Конечные файлы, +ключи и данные иных форматов такой проход не удаляет; ошибка обхода логируется. + +Область изменений + +Основные файлы: lib/file_io.c/h, media_async/encrypted_file.c/h, +chat/chat_attachment.c, chat/chat_msg.c, test_encrypted_file/test_media_primitives +и сетевые проверки зашифрованных вложений. Остальные вызовы file_io_temp_open +в media_async/media_delivery/video переводятся на явные flags=0. +DM не вызывает encrypted_file_write/read: он использует media_crypto streams +и общие sync/publish helper-функции из encrypted_file.h. При правке общего слоя +необходимо сохранить их durable контракт; перенос общих операций в file_io должен +быть прямым, без совместимых обёрток и без изменения DM state machine. +Формат контейнера, nonce, протокол ключей и схема БД для этого фикса не меняются. + +Регрессии и критерии готовности + +- Обе операции: одинаковая строка, /./, hardlink, symlink, отсутствующий/необычный source; + source и прежний target не меняются, alias не удаляется, чужой temp не освобождается. +- Обычные файлы размером 0/1/32767/32768/32769 байт, большой файл, UTF-8 пути; + успешная подготовка не меняет target до publish. Read не публикует info/type при ошибке. +- Внедрение ошибок open/fdopen/read/write/seek/flush/fsync/input-close/output-close/ + rename/directory-sync/RNG; отсутствие утечек fd/памяти/регистраций temp. +- Подмена target на hardlink между подготовкой и publish: повторная проверка + отклоняет публикацию, исходник сохраняется, удаляется только собственный temp. +- Отказ directory sync после rename: новый файл цел, temp-owner отсоединён, + ошибка не представляется как откат; повторная операция корректна. +- Orphan cleanup во время work, после finish, после publish; crash/restart + в обоих деревьях, чужие имена сохраняются. Отмена, stop и удаление канала + до work, после подготовки и после rename: один done, нет публикации сообщения/ready. +- Профильные 20 тестов, ASAN/UBSan/LSan и полный набор. Изменение file_io требует + также проверки его прежних пользователей: copy, video и media blocks/download. + +Это архитектурный проект. Уточнённый фикс ещё не реализован и не тестировался; +успешные 20 тестов ниже относятся к текущей реализации до исправления. Что проверено и что осталось diff --git a/doc/tasks.md b/doc/tasks.md index 3c5868bd..8b3dd56c 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -8,8 +8,11 @@ write/read отвергают только одинаковые строки; /./ и hardlink обходят проверку одного файла. write возвращает0, заменяя исходные30 байт контейнером пустого файла; read возвращает0, заменяя входной ciphertext182 байта plaintext30 байт. Проверены реальные функции, inode и SHA256. - До fopen(wb) проверять идентичность общими file_io-примитивами; один файл — ошибка без изменения/удаления. - Сохранить destination как временный файл вызывающего, публикацию оставить chat_attachment/dm_media. + Архитектурный фикс: один file_io_temp в ak_file; codec готовит закрытый проверенный temp, + worker публикует его одним rename. Target не открывается с усечением; alias проверяется до work и перед publish. + Durability задаётся на весь срок temp; ошибка directory sync после rename не считается откатом. + Cleanup удаляет только собственные артефакты; crash recovery учитывает temp в media и decrypted. + DM не вызывает codec: сохранить его контракт общих sync/publish helpers при изменениях file_io. Детали, проверка архитектуры исправления и незавершённый аудит: [media_async_audit_findings.txt](media_async_audit_findings.txt). Воспроизведение: /tmp/media-encrypted-alias-repro.c, /tmp/media-encrypted-alias-repro.log. Linux-сборка и20/20 профильных тестов PASS; штатный test_encrypted_file не проверяет alias.