Pull to refresh

Comments 16

Про то, что std::filesystem можно использовать, я бы поспорил. В небольших утилитах — да, пожалуйста. Но вот в чём-то большем лучше скрыть за специальным интерфейсом-обёрткой.
Во-первых, такой интерфейс позволит работать с разными реализациями. Хоть с реальной ФС, хоть со временной в памяти, хоть с zip-архивами.
Во-вторых, он позволит проще писать unit-тесты, с нормальными mock'ами и без медленного похода на реальную ФС.
В-третьих, этот интерфейс должен позволять компоненту обращаться к файлам и каталогам только по относительному пути, не позволяя выйти за ограничения, заданные вызывающим кодом, и увеличивая безопасность приложения в целом (тут можно вспомнить WinRAR и недавний CVE-2025-6218).
Реализация для реальных ФС может быть сделана и поверх std::filesystem, но она не будет самой эффективной. Возможно, правильнее потратить пару недель и сделать несколько реализаций для нужных ОС.

третье весьма спорно. Если ты открываешь свои ресурсы, делай это не через ФС, а специальными классами, соответствующими этим ресурсам. А если тебе надо именно ФС, значит ты открываешь файл, пришедший со стороны, и путь у него - абсолютный.

Вы, возможно, меня неправильно поняли. Внутри реализации наших интерфейсов мы пользуемся абсолютными путями, но интерфейс каталога никогда не позволяет выходить за его пределы.

Я привёл пример с уязвимостью в WinRAR. Если бы наша подсистема распаковки не могла выходить за пределы каталога, куда мы распаковываем, то и уязвимости бы не было.

То же относится к кэшам, настройкам, ротации логов, которые всегда живут по определённым путям. Но это не только про создание и удаление файлов — даже если мы открываем каталог на чтение, мы ме должны позволять подсистемам читать данные за его пределами.

Также лучше отделить логику про разное местоположение рабочих каталогов на разных ФС от реальной логики работы, а для этого нам опять нужен интерфейс "каталог" и относительные пути. Плюс не надо думать, в каком виде представлять абсолютный путь в Windows.

Но даже первого достаточно, т.к. если мы можем открыть как реальное дерево с файловой системы, так и архив, то абсолютного пути у нас просто нет.

P.S. А ещё относительные пути производительнее, не надо обрабатывать .. и .

мой нубский взгляд

-1 защититься от этого вы сможете только криптографией и 1 манифестом потомучто если все будут знать хотябы 1 ваш артефакт, будет понимание к атаке, получается только так можно защититься и то придётся ради этого делать хитрости

это не просто закарбчки внутри архиватора получается

тоесь надо отказаться от архиватора и понять какой должен быть манифест см.1

а это накладывает експертность. придётся изолироваться, что-то еще делать, тоесть возможно даже запретить чтение артефактов, и знать какой манифест точно наверно это минимум, плюс должно быть доверие к тому кто закриптовал манифест с артефактактами, кароче интересно и страшно, придётся исключать еще детерминизм ой вот с такими атаками прям страшно реально про архиваторы

Не понимаю. Вот, скажем, в моей подсистеме есть ошибка, и специально созданный архив может модифицировать путь. Но я оперирую относительными путями, а мои интерфейсы каталогов не обрабатывают пути "..". И всё, несмотря на ошибку, создать файл где угодно уже не получится. Почему меня это не спасёт, и нужна криптография? И как криптография тут поможет?

Нельзя защититься от всего, но можно проектировать приложения так, чтобы вероятность ошибки была меньше.

потомучто та цве которую вы прикрепили если её почитать это треш буквально, тот кто пишет архиватор должен доказать когда оно а когда нет, и это корень вообще всей ситуации, тут даже не проблема ошибся ради изучения или чо-то такое где даже нету умысла, а тут тупая ошибка, которая ведёт к изменению даже процессора потомучто наивный запуск не имеет доказательства защиты

мы ругаем с асм 86-64 с++ и не понимаем куда всё идёт из-за такой не защиты

верификация становится тем самым бутылочным горлышком

по нейману, я тупо запускаю ведь, а если мы уходим от неймана, то всё не доказал не запустил

https://github.com/absholi7ly/CVE-2025-6218-WinRAR-Directory-Traversal-RCE?tab=readme-ov-file#2-root-cause-explanation

Буквально "WinRAR позволял добавлять в архивы пути с ..". Т.е. ровно то, о чём я говорю. И описанный выше способ работы с файлами от таких ошибок полностью спасает.

а если кхм, переполнение ценолого инта произойдёт чо делать? так если вдуматься надо чтобы не было такого запуска поидее, это С++ совсем другой

проблема воид, всякие разыменования туда же, это вообще действительно легаси

Мы с вами в двух параллельных вселенных живём. Я говорю о том, как сейчас писать программы так, чтобы ошибок было меньше. Разбиение на независимые части и строгие контракты API этих частей как раз уменьшают сложность системы в целом. И это только один из инструментов, есть и другие. Вы же о том, что авторы должны доказывать, что в их программах нет ошибок. Математически доказывать корректность программ, по крайней мере сейчас, очень долго и дорого и экономически не оправдано. И да, требует применения других языков программирования.

Отвечая про переполнение int'а — используйте Rust и safe-функции, переполнение вызовет panic, а не неопределённое поведение. При желании такие функции и в c++ можно реализовать. Да, будет runtime cost.

Также можно делать type safe обёртки с семантикой, например, "это float в границах [0; 1]". И тогда проверка нужна только при создании значений. А, скажем, при перемножении таких чисел уже можно не проверять.

Вы увели дискуссию в сторону от моей оригинальной мысли о том, что std::filesystem не надо использовать напрямую (и, если уж на то пошло, и chrono, и iostream/fstream). Я не пытался раскрыть тему как писать безопасные программы.

Если ты открываешь свои ресурсы, делай это не через ФС, а специальными классами, соответствующими этим ресурсам.

Это утверждение противоречит статье, т.к. мы хотим делать обобщённый код как для вшитых в приложение, так и для реальных файлов. Ну и код работы с ФС мы точно хотим отделить от классов "текстура" и "меш".

Хоть с реальной ФС, хоть со временной в памяти, хоть с zip-архивами.

Так для этого и сделаны стримы

Нам может быть нужно больше всего — мапить в память, создавать и удалять файлы, смещать файловые указатели. Стримы очень ограниченные.

Рассуждения о плохости неудачности дизайна std::filesystem выглядят пустопорожними, потому что нет примеров того, как это должно было бы выглядеть по мнению автора статьи.

R = x^8 * x^4 * _ * x^1

выглядит красиво если не задумываться. А если подумать то здесь что-то не то! Как две операции умножения

* _ *

превратились в одну или подчерк это единица, почему тогда не написана единица, где то было умножение на единицу? Вроде бы нет.

Сталкивался с тем, что std::filesystem::is_symlink() не работала на Linux: всегда возвращала false.
На некоторых BSD нет конструктора std::filesystem::path(const std::wstring &).
Так что, так себе интерфейс получился...
Малопригоден для жизни.

Sign up to leave a comment.

Articles