EreTIk's Box » Cтатьи, исходники » Подводные камни использования флага IO_OPEN_TARGET_DIRECTORY (SL_OPEN_TARGET_DIRECTORY)


Для получения описателя на файловый объект ядро экспортирует функцию IoCreateFile. Ядро Windows Vista и старше так же экспортирует расширенную версию этой функции IoCreateFileEx. Обе эти функции могут использоваться драйвером для открытия или создания файла, потока или директории. Рассмотрим параметр Options этих функций. Это набор флагов, часть которых документированы:

  • IO_NO_PARAMETER_CHECKING - флаг, сигнализирующий ядру о том, что не нужно проверять входные буферы Probe-функциями. Реализуется это довольно элегантно: режим вызывающего кода принудительно приравнивается к KernelMode
  • IO_FORCE_ACCESS_CHECK - флаг, перекрывающий IO_NO_PARAMETER_CHECKING, который приводит к тому, что ядро проверяет все буферы, даже если режим вызывающей нити KernelMode
  • IO_IGNORE_SHARE_ACCESS_CHECK - флаг, сигнализирующий ядру о том, что нужно игнорировать проверки разделяемого открытия

Как видно из вышеприведенного списка документированы те флаги, которые обрабатываются ядром, хотя тоже не все. Но среди этих флагов также можно указать IO_OPEN_TARGET_DIRECTORY ( = 4), объявление которого присутствует в заголовочном файле ntifs.h. Этот флаг приходит в файловую систему в виде равного ему по значению флага SL_OPEN_TARGET_DIRECTORY в поле IrpSp->Flags.


Этот флаг сигнализирует драйверу файловой системы о том, что необходимо открыть родительскую директорию запрашиваемого объекта. Этот флаг, видимо, был введен для упрощения механизмов переименования файлов и создания жестких ссылок. Если в структуре флага FILE_RENAME_INFORMATION заполнено поле родительской директории или новое имя указано в полном формате (первый символ слеша), ядро открывает родительскую директорию целевого имени, используя флаг IO_OPEN_TARGET_DIRECTORY. Для этого в ядре реализована не экспортируемая функция IopOpenLinkOrRenameTarget. Эта же функция проверяет, что открытая директория расположена на том же томе, что и переименовываемый файл: сравнивается идентичность указателей, полученных вызовом IoGetRelatedDeviceObject для целевого файла и открытой директории. Именно открытый таким образом объект файла записывается в IrpSp->Parameters.SetFile.FileObject.


И вот, казалось бы, замечательная недокументированная возможность: где-то можно отказаться от ручного разбора символических имен и открывать директории, не используя обратный поиск слеша. Но не все так безоблачно. Предположение о том, что флаг IO_OPEN_TARGET_DIRECTORY был введен для поддержки переименования и создания жестких ссылок подтверждается, если заглянуть в исходный код CDFS’а (DDK~\src\filesys\cdfs\Win7\create.c):


    //
    //  Do some preliminary checks to make sure the operation is supported.
    //  We fail in the following cases immediately.
    //
    //      - Open a paging file.
    //      - Open a target directory.
    //      - Open a file with Eas.
    //      - Create a file.
    //

    if (FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE | SL_OPEN_TARGET_DIRECTORY) ||
        (IrpSp->Parameters.Create.EaLength != 0) ||
        (CreateDisposition == FILE_CREATE)) {

        CdCompleteRequest( IrpContext, Irp, STATUS_ACCESS_DENIED );
        return STATUS_ACCESS_DENIED;
    }

                

То есть на CDFS невозможно использовать IO_OPEN_TARGET_DIRECTORY, драйвер всегда возвращает STATUS_ACCESS_DENIED. С дугой стороны, в исходниках FAT’а все нормально. В обработчике IRP_MJ_CREATE FatCommonCreate флаг обрабатывается корректно: вызывается функция FatOpenTargetDirectory.


Для тестов я написал небольшую утилиту IotdDemo, включающую в себя консольное приложение IotdCon и драйвер iotd.sys. Утилиту необходимо запускать с одним параметром: путь к целевому файлу. Работа demo-утилиты довольно проста:

  • IotdCon открывает описатель указанного в командной строке файла
  • IotdCon распаковывает, стартует драйвер iotd.sys и отсылает полученный описатель в драйвер
  • iotd.sys получает полное NT-имя файла вызовом ObQueryNameString()
  • iotd.sys вызывает открытие родительской директории IoCreateFile(... , IO_OPEN_TARGET_DIRECTORY) на имя, полученное на предыдущем шаге. Так как вызов происходит в контексте того же процесса, то в случае успеха будет создан описатель открытой директории для текущего процесса. Созданный описатель возвращается в IotdCon
  • IotdCon получает NT-имя по открытому описателю вызовом NtQueryObject(..., ObjectNameInformation, ...) и выводит его в консоль
  • IotdCon перечисляет элементы директории вызовом ZwQueryDirectoryFile(..., FileBothDirectoryInformation, ...) и печатает их в консоль

Итак, запускаем на WinXP-SP3-i386. Файловые системы FAT и NTFS не преподносит никаких сюрпризов. HGFS (Shared Folders для VMware) тоже замечательно проходит этот тест. CDFS, как и ожидалось, возвращает STATUS_ACCESS_DENIED.


А вот редиректор Oracle VM VirtualBox (тестировались версии 3.2.6 и 3.2.8) отреагировал на такой тест своеобразно. Странности начинаются с того, что при попытке получить имя на возвращенный описатель вызовом NtQueryObject(..., ObjectNameInformation, ...) всегда возвращается строка "\Device\VBoxMiniRdr". При этом неважно на каком уровне вложенности находился целевой файл, возвращаемая строка всегда одна и та же. Далее, при попытке перечислить объекты внутри директории вызовом ZwQueryDirectoryFile(..., FileBothDirectoryInformation, ...), всегда возвращается ошибка со статусом STATUS_OBJECT_NAME_INVALID(0xC0000033). Точно такое же поведение было замечено и при тестировании редиректора SMB, драйвер которого поставляется вместе с Windows. Единственное что отличает - строка получаемого NT-имени, которая в этом случае оказалась "\Device\LanmanRedirector".


Что бы картина была более полной, запустим те же самые тесты на Win7-i386. Тем более, что начиная с Windows Vista, архитектура редиректоров сетевых ФС притерпела некоторые изменения (подробнее о них можно прочесть в MSDN’овской статье MUP Changes in Microsoft Windows Vista). NTFS, FAT и HGFS все так же прекрасно отработали с этим тестом на Windows 7. CDFS, как и ожидалось, вернул STATUS_ACCESS_DENIED. Редиректор Oracle VM VirtualBox тоже показал стабильность: NT-имя купировано и перечисление элементов директории возвращает ошибку STATUS_OBJECT_NAME_INVALID. А вот дела с SMB-редиректором обстоят намного хуже. Имя возвращается всегда "\Device\Mup", что не удивительно (в MSDN об этом написано в статье об изменениях в MUP’е), а вот перечисление элементов директории вызывает BSOD!


Причем, если удаленная машина Windows XP, то падает mrxsmb10.sys.


Call stack падения:

mrxsmb10!MRxSmbQueryDirectory+0x39 mrxsmb!SmbShellQueryDirectory+0x1b rdbss!RxQueryDirectory+0x4ff rdbss!RxCommonDirectoryControl+0xad rdbss!RxFsdCommonDispatch+0x646 rdbss!RxFsdDispatch+0x1ab mrxsmb!MRxSmbFsdDispatch+0x9a nt!IofCallDriver+0x63 mup!MupiCallUncProvider+0x10f mup!MupStateMachine+0x9b mup!MupFsdIrpPassThrough+0x93 nt!IofCallDriver+0x63 fltmgr!FltpLegacyProcessingAfterPreCallbacksCompleted+0x2aa fltmgr!FltpDispatch+0xc5 nt!IofCallDriver+0x63 nt!IopSynchronousServiceTail+0x1f8 nt!NtQueryDirectoryFile+0x5b nt!KiFastCallEntry+0x12a ntdll!KiFastSystemCallRet ntdll!NtQueryDirectoryFile+0xc

А если на удаленном хосте установлена ОС Windows 7, то падает mrxsmb20.sys.


Call stack падения:

mrxsmb20!MRxSmb2EnumerateDirectoryFromCache+0x1f mrxsmb20!MRxSmb2QueryDirectory+0x14 mrxsmb!SmbShellQueryDirectory+0x1b rdbss!RxQueryDirectory+0x4ff rdbss!RxCommonDirectoryControl+0xad rdbss!RxFsdCommonDispatch+0x646 rdbss!RxFsdDispatch+0x1ab mrxsmb!MRxSmbFsdDispatch+0x9a nt!IofCallDriver+0x63 mup!MupiCallUncProvider+0x10f mup!MupStateMachine+0x9b mup!MupFsdIrpPassThrough+0x93 nt!IofCallDriver+0x63 fltmgr!FltpLegacyProcessingAfterPreCallbacksCompleted+0x2aa fltmgr!FltpDispatch+0xc5 nt!IofCallDriver+0x63 nt!IopSynchronousServiceTail+0x1f8 nt!NtQueryDirectoryFile+0x5b nt!KiFastCallEntry+0x12a ntdll!KiFastSystemCallRet ntdll!NtQueryDirectoryFile+0xc

Вывод из всего этого можно сделать довольно очевидный: недокументированные (или не полностью документированные) возможности ядра ОС стоит использовать только на узком наборе ситуаций, которые были заранее протестированы и проверены. В данном случае можно с успехом использовать IO_OPEN_TARGET_DIRECTORY, но только будучи абсолютно уверенным, что целевая файловая система FAT, NTFS или HGFS. Пожалуй, удобство этого флага при открытии стоит того, что бы осторожно применять его, избавляя себя в некоторых случаях от анализа символических имен.




Скачать demo-утилиту IotdDemo с исходным кодом.


ΞρεΤΙκ