Прочитав обзор моего собственного кода , ответ о boost::filesystem::unique_path , еще один ответ о unique_path и ссылки на него, документацию о tmpnam и ее справочную страницу Linux, и и еще несколько связанных с этим вещей. У меня сложилось впечатление, что с C++23 у нас есть все необходимое для обеспечения безопасной реализации создания временных файлов, только с использованием стандартной библиотеки C++23.
Под безопасным я, среди прочего, подразумеваю реализацию, которая не имела бы известных уязвимостей безопасности tmpnam
и boost::filesystem::unique_path
.
Например, мы могли бы реализовать class temp_file
, конструктор которого будет генерировать 128-битное случайное имя файла и открывать файл с этим именем, расположенный под std::filesystem::temp_directory_path()
, в эксклюзивном режиме (std::ios::noreplace
), чей деструктор закроет файл и удалит его, и чей stream
Функция-член вернет ссылку на поток ввода/вывода открытого файла.
Может ли такая реализация, если она правильная, рассматриваться как безопасное средство для создания и использования временных файлов, или я упускаю некоторые важные соображения, и в этом случае, каковы эти соображения?
Итак, для начала вам нужно полагаться на детали качества реализации в стандартной библиотеке и предположения об API базовой файловой системы, используемом в реализации стандартной библиотеки. Многих состояний гонки можно избежать только в том случае, если работа файловой системы уже является атомарной на уровне операционной системы, так что стандартная библиотека не может предоставить гарантии гонки, даже если бы захотела.
Рандомизация имени файла с высокой энтропией не помогает избежать уязвимости, поскольку другой процесс может просмотреть файлы в каталоге. Это только предотвращает незлонамеренное вмешательство.
@user17732522 user17732522, мы говорим о одновременном доступе к записи во временный файл, который приводит к неопределенному состоянию файла? Но чем это отличается от одновременного доступа к любому другому файлу? Почему для временных файлов это будет большей проблемой, чем для других файлов? Кроме того, какая альтернатива не имела бы этой проблемы? mkstemp
, например, не облегчило бы ситуацию, верно?
@user17732522, другими словами, я не понимаю, как то, о чем вы говорите, может сделать предлагаемую реализацию менее безопасной, чем создание и использование любого другого файла в файловой системе.
Я говорю, что ни одна операция файловой системы не может работать правильно при взаимодействии нескольких потоков/процессов, в частности, нет гарантии, что операции являются атомарными на уровне файловой системы. Например, внутреннее открытие нового файла может состоять из двух этапов: создание файла и открытие вновь созданного файла. Если вмешивается другой процесс, для функции стандартной библиотеки не существует определенного поведения. Конечно, пока ОС это позволяет, я ожидаю, что разработчик библиотеки по-прежнему будет пытаться обеспечить реализацию без гонок.
И да, это проблема для любой параллельной файловой операции, а не только для временных файлов. С ними ситуация немного хуже, поскольку они могут быть созданы в каталоге, доступном всем пользователям.
@user17732522 user17732522, для таких нетривиальных вопросов, я думаю, цитирование стандарта, как это сделали вы, необходимо. Фактически, я прочитал некоторые части главы «Файловые системы» в поисках информации об атомарности, но не нашел ее. Вы можете рассмотреть возможность суммирования своих комментариев в ответе. Кроме того, это потрясающе, я даже не предполагал, что все настолько плохо.
Как это могло быть основано на мнении, если я и мои респонденты ДОКАЗЫВАЛИ, что единственный возможный ответ на этот вопрос — «нет», цитируя соответствующую часть стандарта?!
Возможно, людям, которые закрыли это, следует связаться с рабочей группой по библиотекам C++, которая считает, что «Проблемы безопасности, вероятно, лучше всего решить в следующем TS файловой системы, поскольку необходимо полномасштабное предложение, и, вероятно, потребуется несколько лет. развиваться». Я уверен, что они были бы рады услышать, что проблемы TOCTTOU, которые они пытаются решить, на самом деле могут не существовать, в зависимости от мнения.
API файловой системы C++ не может создать «безопасную» версию этой операции.
Чтобы иметь полностью безопасную версию, вам необходимо иметь возможность выполнять все следующие действия как одну атомарную операцию файловой системы:
Стандартная библиотека может выполнять каждую из этих операций по отдельности, но уязвимость заключается в невозможности выполнить всю последовательность как единый атомарный процесс. Пока какая-либо другая программа может вмешаться в любую из этих операций, уязвимости существуют. А в C++ нет механизма, обеспечивающего такую атомарность файловой системы.
Если вы хотите это сделать, то на данный момент вам необходимо использовать библиотечную функцию, специфичную для ОС.
Я не совсем слежу за этим. Если я создаю std::fstream из случайного пути к файлу с флагом std::ios::noreplace, не выполняю ли я атомарно и 2, и 3? Кроме того, файл может быть создан злоумышленником между 1 и 2, но тогда шаг 2/3 завершится неудачей, верно? Т.е. атака не осталась незамеченной?
@nilo: Возможно, вы выполняете 2 и 3 атомарно. Но опять же, в зависимости от используемой ОС и файловой системы, возможно, и нет. Вот лишь один довольно известный пример: примерно до версии 2.6 или около того O_EXCL на томе NFS из Linux не был атомарным.
@JerryCoffin, в своем «только для одного достаточно известного примера» вы, похоже, имеете в виду официально задокументированное ограничение системного вызова open
, а именно то, что O_EXCL
не поддерживался до NFSv3 и ядра 2.6. На мой взгляд, если в такой системе вы создаете std::fstream
с флагом std::ios::noreplace
и построенный поток сообщает, что файл открыт, то в реализации стандартной библиотеки есть ошибка. Это не означает, что конструкция std::fstream
семантически неатомарна.
@nilo: Ну, я полагаю, что это в любом случае спорно - я сомневаюсь, что кто-нибудь будет пытаться портировать C++23 на эту старую версию LInux.
Не читая раздел «Поведение файловой системы» в стандарте C++, который легко пропустить, я думаю, разработчик мог бы разумно ожидать, что std::fstream::open
является атомарным, как и должно быть, если бы не практические препятствия, которые этому препятствуют. . Я думаю, что это делает этот ответ запутанным, поскольку он не объясняет, почему реализация может иметь такие недостатки и при этом соответствовать стандарту. Если бы такое разъяснение было предоставлено, я мог бы принять ответ.
Я был бы рад проголосовать за это, теперь, когда я понимаю это лучше, но, поскольку после моего отрицательного голосования оно не было обновлено, я, к сожалению, не могу. Судя по всему, StackOverflow не думает, что можно изменить свое мнение об ответе после некоторого времени созревания. Ну что ж.
Ответ Никола Боласа в сочетании с комментариями пользователя 17732522 позволяет мне составить самодостаточный ответ, основанный на стандарте C++.
Можно ли такую реализацию, если она правильная, рассматривать как безопасное средство для создания и использования временных файлов, или я пропустил некоторые важные соображения, и в этом случае, каковы они соображения?
Окончательный вариант C++23 содержит:
Поскольку мы никогда не можем гарантировать, что отдельный поток или процесс не получит доступ к временному файлу, пока он создается и к нему обращается пользователь средства, такое средство не может считаться безопасным.
Такое отсутствие гарантированной безопасности на самом деле является общим для файловых потоков C++, но, как выразился пользователь17732522, «для [временных файлов] это немного хуже, поскольку они могут быть созданы в каталоге, доступном всем пользователям».
Что касается альтернатив, то в стандарте C++ есть tmpfile
, который не раскрывает имя файла и, следовательно, по крайней мере теоретически может быть безопаснее за счет использования FILE
API вместо std::fstream
.
Существуют также альтернативы для конкретной ОС.
Я думаю, что tmpfile
в большинстве случаев безопасно использовать даже на уровне спецификации, по крайней мере, в последних версиях C и C++. Указано, что оно ведет себя так, как если бы файл открывался с флагом x
, что, в свою очередь, гарантированно является атомарной операцией или завершается сбоем согласно C §7.23.5.3. Вероятно, все еще может быть осуществлена атака типа «отказ в обслуживании», если угадать имена, которые предпринимаются. Ни C, ни POSIX, похоже, не гарантируют, что свободное имя также будет получено атомарно. Для этого потребуется полностью реализовать операцию на уровне ОС. В ОС, которая не допускает атомарности, tmpfile
всегда должен давать сбой.
@ user17732522, большое спасибо за эту информацию. Я подозревал отсутствие гарантии, отсюда и моя осторожная формулировка.
Мне придется снова исправиться. Судя по всему, эта гарантия появилась в C23 и еще не включена ни в одну версию C++. Насколько я могу судить, раньше, насколько я могу судить, вообще не было никакой гарантии атомарности ни в C, ни в POSIX.
К сожалению, стандарт не дает никаких гарантий относительно поведения нескольких потоков или процессов, одновременно работающих с одним и тем же объектом файловой системы. Ни одна из операций файловой системы не будет работать корректно, если вмешается другой процесс или даже другой поток: eel.is/c++draft/fs.race.behavior