mirror of
https://github.com/Nekrolm/ubbook.git
synced 2026-06-09 13:14:18 +03:00
filesystem
This commit is contained in:
@@ -114,6 +114,7 @@
|
||||
6. [condition_variable](concurrency/condition_variable.md)
|
||||
7. [Гонки за vptr](concurrency/vptr.md)
|
||||
8. [std::async](concurrency/std_async.md)
|
||||
9. [Файловая система](concurrency/filesystem.md)
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Конкурентный доступ к файловой системе
|
||||
|
||||
Условия гонки за ресурсы могут возникать на разных уровнях:
|
||||
- внутри одной программы
|
||||
- между несколькими программами на одном компьютере
|
||||
- между разными программами на разных компьютерах
|
||||
|
||||
И почти всегда это нежелательная, ошибочная ситуация, последствия которой варьируются от просто неправильного результата до чудовищных уязвимостей в безопасности.
|
||||
|
||||
Иногда, конечно, система может быть толерантна к таким ошибкам и серьезных проблем не возникнет: например, если страничка магазина запрашивает список товаров на складе, а в этот момент база данных склада обновляется, то вы, как пользователь, возможно, увидите неполный или устаревший список товаров или смесь из старых и новых данных. Но критического ничего не произойдет, если разработчики предусмотрели дополнительные проверки с запросом к базе данных в момент взаимодейтсвия с конкретным товаром из списка... Чтобы вы не купили то, чего больше не существует.
|
||||
|
||||
Файловая система — один из таких ресурсов, гонки за которым естественны и должны учитываться при разработке приложений. Да, самые низкоуровневые проблемы синхронизации чтения и записи в файловую систему берет на себя операционная система и (или) конкретный драйвер. Можно «спокойно» одновременно читать и писать в один и тот же файл и получать мусор: атомарные низкоуровневе операции `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
|
||||
// это упрощенный код
|
||||
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)
|
||||
Reference in New Issue
Block a user