Баг в NTFS, или как подвесить всю систему
Май, 2017
Не так давно при разработке фильтра файловых систем возникла проблема, которая приводила к подвисанию всей системы. Фильтр выполнял очень простые действия и сам был примитивным. Чтобы выяснить причину, пришлось спуститься до отладки и реверс-инжиниринга драйвера NTFS. Анализ выявил очень интересный эффект: если скомпилировать и выполнить очень простую программу, изображённую на рисунке ниже, то доступ к соответствующему тому подвиснет.

В данном примере, если попытаться открыть любой файл относительно файла $mft, доступ ко всему тому “C” повиснет, а так как этот том системный — подвиснет и вся система. При этом не нужно иметь каких-либо прав. Если том не системный, повиснет только доступ к нему, но перезагрузка тоже зависнет.
Немного теории
Заголовок раздела «Немного теории»Когда процесс открывает файл, кроме полученного HANDLE, в пространстве ядра формируются структуры, представляющие файл тома в памяти (см. рисунок).

HANDLE файла всегда ссылается на структуру ядра FILE_OBJECT, которая формируется ядром перед посылкой запроса файловой системе, а файловая система инициализирует её поля. FILE_OBJECT содержит указатели на структуры файловой системы: FCB (File Control Block — все данные для управления файлом) и CCB (Context Control Block — данные, уникальные для конкретного открытого экземпляра). Два разных HANDLE могут ссылаться на один и тот же файл тома. Для каждого открытого файла в памяти будет ровно одна структура FCB; если файл открыт несколько раз — столько структур CCB, сколько раз он открыт, и все они ссылаются на единственную FCB.
Параллельные операции доступа к файлу должны быть сериализованы: некоторые (чтение) могут выполняться одновременно, другие (запись) — только монопольно. Для этого ядро предоставляет механизм ERESOURCE, который может быть захвачен монопольно или разделяемо. Монопольный захват ставит любые другие попытки в очередь ожидания; разделяемый — удовлетворяется сразу, но только пока очередь ожидания пуста. Структуры FCB файловых систем содержат эти механизмы и активно пользуются ими — так обеспечивается целостность файла в памяти и на томе.
Файл $mft файловой системы NTFS — системный, он описывает расположение всех файлов на томе. NTFS открывает его при монтировании для личного использования: чтение директории или открытие файла вызывает чтение $mft, удаление или создание файла — запись в него. Перед любой такой операцией ERESOURCE этого файла захватывается, а после операции освобождается.
Функция NtfsCommonCreate
Заголовок раздела «Функция NtfsCommonCreate»Чтобы понять суть проблемы, нужно понимать принцип работы функции NtfsCommonCreate (упрощённый псевдокод — на рисунке; приведены только относящиеся к проблеме части).

NTFS хранит дерево уже открытых файлов/директорий, поэтому целевой файл целесообразно найти в этом дереве, а не читать том многократно. Функция NtfsFindStartingNode пытается найти файл, а если не удалось — директорию, ближайшую к нему по глубине (вплоть до корня), и возвращает указатель на её FCB плюс часть необработанного пути. Найденный файл/директория предварительно захватывается разделяемо.
Если необработанной части пути нет — файл найден, работа завершается. Иначе функция продолжает поиск на томе в цикле: последовательно открываются директории, ведущие к файлу, через NtfsOpenSubdirectory, которая захватывает открытый файл/директорию монопольно (предыдущая при этом освобождается). Если очередной элемент пути не директория — цикл завершается с ошибкой.
При неуспешном завершении NtfsCommonCreate закрывает последнюю найденную директорию функцией NtfsTeardownStructures, которая также освободит ERESOURCE директории/файла, если они не являются открытыми. Поскольку директория/файл открыты файловой системой только что, вероятнее всего, ERESOURCE будет освобождён, а FCB закрыт.
Суть проблемы
Заголовок раздела «Суть проблемы»При попытке открыть файл относительно файла $mft функция NtfsFindStartingNode не найдёт его (она ищет иначе, чем NtfsOpenSubdirectory, которая находит этот файл всегда). Начинает работать цикл с корня файловой системы: NtfsOpenSubdirectory открывает $mft и захватывает его ERESOURCE монопольно. На следующей итерации цикл обнаруживает, что файл не является директорией, и прерывается с ошибкой. Тогда NtfsCommonCreate через NtfsTeardownStructures пытается закрыть его.
Но NtfsTeardownStructures не может закрыть файл, т.к. он открыт самой файловой системой при монтировании, и — вопреки ожиданиям — не освобождает ERESOURCE файла $mft. Он остаётся захваченным навсегда. Поэтому при любой попытке создания или чтения файлов тома NTFS попытается захватить ERESOURCE $mft и зависнет на этом этапе.
Заключение
Заголовок раздела «Заключение»Данную проблему нельзя назвать уязвимостью, но, имея удалённый доступ к машине, возможно нарушить её работу. Ошибка сохраняется вплоть до последних версий Windows (начиная как минимум с Windows Vista), за исключением последних обновлений. Описание работы NTFS здесь сильно упрощено и отражает только суть проблемы — реальная реализация намного сложнее.
Это краткая аннотация статьи, полную версию читайте в первоисточнике.
Первоисточник: Блог Аладдин на Хабре, май, 2017
Эксперт: Блог Аладдин на Хабре
