Files
ubbook/concurrency/filesystem.md
Sergey Fukanchik 8c1499929a Fix a number of typos (#128)
Co-authored-by: Sergey Fukanchik <s.fukanchik@postgrespro.ru>
2025-09-29 15:13:45 +01:00

63 lines
6.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Конкурентный доступ к файловой системе
Условия гонки за ресурсы могут возникать на разных уровнях:
- внутри одной программы
- между несколькими программами на одном компьютере
- между разными программами на разных компьютерах
И почти всегда это нежелательная, ошибочная ситуация, последствия которой варьируются от просто неправильного результата до чудовищных уязвимостей в безопасности.
Иногда, конечно, система может быть толерантна к таким ошибкам и серьезных проблем не возникнет: например, если страничка магазина запрашивает список товаров на складе, а в этот момент база данных склада обновляется, то вы, как пользователь, возможно, увидите неполный или устаревший список товаров или смесь из старых и новых данных. Но критического ничего не произойдет, если разработчики предусмотрели дополнительные проверки с запросом к базе данных в момент взаимодействия с конкретным товаром из списка... Чтобы вы не купили то, чего больше не существует.
Файловая система — один из таких ресурсов, гонки за которым естественны и должны учитываться при разработке приложений. Да, самые низкоуровневые проблемы синхронизации чтения и записи в файловую систему берет на себя операционная система и (или) конкретный драйвер. Можно «спокойно» одновременно из разных процессов читать и писать в один и тот же файл и получать мусор или штатные ошибки: низкоуровневые операции `read` и `write` будут как-то упорядочены планировщиком ввода-вывода. С самой файловой системой при этом всё будет в порядке.
Но высокоуровневые операции над файловой системой в рамках вашей бизнес-логики требуют внимания и осторожности. Ведь самый простой способ получить условие гонки — добавить проверки при открытии файла!
```C++
#include <filesystem>
#include <string>
#include <fstream>
namespace fs = std::filesystem;
int main() {
if (!fs::exists("/my/file")) {
return EXIT_FAILURE;
}
std::ifstream input("/my/file");
std::string line; input >> line;
// do something
return EXIT_SUCCESS;
}
```
Это класcическая **TOCTOU** (*Time-of-Check-Time-of-Use*) ошибка. Между проверкой и открытием файла, файл может быть удален.
Но причем тут С++, если такая проблема существует для любого языка программирования?
Действительно. Например, во всех версиях стандартной библиотеки Rust с 1.0 до 1.58.1 [была](https://blog.rust-lang.org/2022/01/20/cve-2022-21658.html) похожая ошибка в реализации функции `remove_dir_all`.
```Rust
// это упрощенный код
// directory: Path
if directory.is_symlink() {
remove_link(directory)
} else {
remove_recursive(directory)
}
```
Между проверкой и удалением злоумышленник мог подменить настоящий каталог на символьную ссылку и добиться удаления данных, к которым у него нет доступа.
Ошибку исправили: вместо работы с путями, функция стала работать с элементами в каталоге через единожды открытываемый файловый дескриптор.
А теперь мы можем вернуться к C++:
В std::filesystem также есть функция `remove_all`, с тем же значением что и версия из Rust. И в большинстве ее реализаций в 2022 году также нашли точно такую же [ошибку](https://issuetracker.google.com/issues/42410010?pli=1)!
Обсуждение этой ошибки было довольно [бурным](https://www.reddit.com/r/cpp/comments/s8ok0h/possible_toctou_vulnerabilities_in/), поскольку стандарт C++ объявляет неопределенным поведением любые вызовы функций std::filesystem, приводящие к гонке!
Но они все приводят к гонке! Их корректная работа зависит только от благонадежности других приложений, обращающихся к той же файловой системе. Можно ли в таком случае ничего не исправлять в функции `std::filesystem::remove_all`?
Конечно нужно! Ошибка была исправлена в GCC версии [11.5](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104161) и в [Clang-14](https://github.com/llvm/llvm-project/commit/4f67a909902d8ab9e24e171201db189b661700bf)