Design owned temporary output and durable publication for encrypted attachments
This commit is contained in:
1 parent
8cc97d2ffe
commit
2e3ab00ce6
2 files changed
+128
-20
No files matched your search
@@ -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 нужно удалять оставшиеся временные файлы
|
||||
строгого собственного формата <UUID>.uenc.tmp.<32hex> / <UUID>.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 тестов ниже относятся к текущей реализации до исправления.
|
||||
|
||||
Что проверено и что осталось
|
||||
|
||||
|
||||
+5
-2
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user