hex.pp.ua

Баг в функции BackupSeek

BackupSeek неправильно пропускает разреженный блок




Во время разработки следующей версии программы NTFS Stream Explorer я столкнулся с багом в WinAPI-функции BackupSeek. Для чтения потоков NTFS, содержащихся в файле можно использовать две стандартные функции BackupRead и BackupSeek. Эти функции перечисляют все его содержащиеся в нём системные потоки. Информация о каждом потоке содержится в структуре WIN32_STEAM_ID. Я написал вспомогательную утилиту NTFS BackupRead Dumper, которая умеет делать дамп всех потоков файла при помощи BackupRead и восстанавливать файл из дампа.

typedef struct _WIN32_STREAM_ID {
        DWORD          dwStreamId ;
        DWORD          dwStreamAttributes ;
        LARGE_INTEGER  Size ;
        DWORD          dwStreamNameSize ;
        WCHAR          cStreamName[ ANYSIZE_ARRAY ] ;
} WIN32_STREAM_ID, *LPWIN32_STREAM_ID ;

Три самых главных поля в структуре WIN32_STREAM_ID это dwStreamId, Size и dwStreamNameSize. Анализ файла происходит следующим образом: с помощью BackupRead читается структура WIN32_STREAM_ID, по полю dwStreamId выясняется, что за поток перед нами, далее его можно либо прочитать с помощью той же BackupRead, либо пропустить его с помощью BackupSeek и перейти к следующему. Если мы решили читать поток, то в первую очередь надо проверить, именованый он или неименованый. Среднестатистический файл содержит несколько системных потоков, причём все из них безымянные, соответственно dwStreamNameSize равно 0. Некоторые файлы содержат именованные потоки типа BACKUP_ALTERNATE_DATA, это те самые «альтернативные файловые потоки», о которых много информации в сети. Если dwStreamNameSize не равно нулю, считываем имя потока, если Size тоже не равно нулю, значит поток не пустой, и его содержимое можно прочитать.

Для примера был создан пустой файл размером 64 Кб, ему был присвоен разреженный атрибут и задан диапазон разреженности 0-65536. У файла был создан альтернативный поток, названный STREAM с содержимым AAAA. После этого файлу был присвоен один расширенный атрибут, названный ATTR и содержащий BBBB. Расширенный атрибут был присвоен с помошью утилиты EA.EXE, а альтернативный поток был создан прямо из командной строки.

D:\TMP>fsutil file createnew test.dat 65536
Файл D:\TMP\test.dat создан
D:\TMP>fsutil sparse setflag test.dat

D:\TMP>fsutil sparse setrange test.dat 0 65536

D:\TMP>echo AAAA > test.dat:stream

D:\TMP>ea set test.dat ATTR BBBB

На картинке показано, как BackupRead видит файл, содержащий все эти атрибуты. Цветами выделены заголовки атрибутов, соответствующие структуре WIN32_STREAM_ID.

Структура потоков NTFS
Двоичное представление потоков NTFS

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

#define BackupClose(x,y) BackupRead(x, NULL, 0, NULL, TRUE, FALSE, y)
  
WIN32_STREAM_ID sid;
LPVOID lpContext = NULL;

while (TRUE)
{
  BackupRead(hFile, (LPBYTE)&sid, dwStreamHeaderSize,
                  &dwRead, FALSE, TRUE, &lpContext);

  // Условие выхода из цикла
  if (0 == dwRead)
  {
    break;
  }

  // Тут можно вывести какую-либо информацию о потоке, взятую
  // из WIN32_STREAM_ID

  // Если поток именованный
  if (sid.dwStreamNameSize > 0)
  {
    // с помощью BackupRead можно прочитать имя потока
  }

  // Если поток имеет не нулевой размер
  if (sid.Size.QuadPart > 0)
  {
    // с помощью BackupRead можно прочитать содержимое потока
    
    // или, если это не требуется, переместиться к следующему потоку
    BackupSeek(hFile, sid.Size.LowPart, sid.Size.HighPart,
                &dw1, &dw2, &lpContext);
  }

  //Очищаем структуру перед следующим проходом цикла
  ZeroMemory(&sid, sizeof(WIN32_STREAM_ID));
}
  BackupClose(hFile, &lpContext);

Как выяснилось, функция BackupSeek в таком алгоритме прекрасно работает со всеми видами потоков, за исключением потока типа BACKUP_SPARSE_BLOCK. Такие потоки существуют у разреженных файлов NTFS. Баг в функции BackupSeek приводит к тому, что указатель для чтения перемещается в некорректную позицию. Из-за этого следующий в цикле вызов BackupRead прочитает не структуру WIN32_STREAM_ID, а часть содержимого потока разреженного файла. Соотвественно, алгоритм не может распознать в структуре тип потока и не может далее производить их перечисление. Все потоки, стоящие в файле после BACKUP_SPARSE_BLOCK становятся невидимыми для программы, которая занимается перечислением потоков. Саму операционную систему Windows это не затрагивает, так как она, разумеется, обрабатывает внутренности файла далеко не с помощью функций резервного копирования.

Вывод: функция BackupSeek не может корректно пропустить поток, если он имеет тип BACKUP_SPARSE_BLOCK.

Решение проблемы

Чтобы осуществить корректное перечисление потоков, приходится добавить в алгоритм дополнительное условие.

if (BACKUP_SPARSE_BLOCK == sid.dwStreamId)
{
  // Баг в BackupSeek не позволяет пропустить разреженный блок.
  // Читаем его, не обрабатывая.

  sparse_buf = (LPBYTE) GlobalAlloc(GPTR, BUF_SIZE);
  BackupRead(hFile, sparse_buf, sid.Size.LowPart, &dwRead,
                FALSE, TRUE, &lpContext);
  GlobalFree(sparse_buf);
} else 
{
  //Перемещаемся к следующему потоку
  BackupSeek(hFile, sid.Size.LowPart, sid.Size.HighPart,
              &dw1, &dw2, &lpContext);
}

В отличие от BackupSeek, функция BackupRead читает именно столько байт, сколько указано в sid.Size, и после операции чтения указатель в файле оказывается на правильной позиции. Это позволяет перечислять потоки дальше.

В итоге в бета-версии NTFS Stream Explorer 1.03 сделано корректное отображение потоков, даже если открыт разреженный файл.

Потоки в NTFS Stream Explorer
Список потоков в NTFS Stream Explorer

Кратко

Ошибка:
Неправильная работа функции BackupSeek при обработке разреженных файлов

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

Операционная система:
ОС Windows семейства NT

Компонент:
Библиотека kernel32.dll, функция BackupSeek

По теме разреженных файлов также есть следующее:

система комментирования CACKLE

Автор: амдф
Дата: 12.03.2011


Разделы сайта
Главная
Блог
Native API
NTFS и ReFS
Микроконтроллеры
Справочник NTDLL
Коды NTSTATUS
Разное

Избранное
NTFS Stream Explorer
Native Shell
Тенгвар

Остальное
nvpnhcknn (архив)
English pages
Контакты

Ленты atom
Лента Atom сайта Лента Atom блога



При копировании материалов хорошим тоном будет указание авторства и ссылка на сайт.