Когда я пытаюсь получить размер физического сектора через DeviceIoControl, я получаю доступ запрещен

Из моего приложения веб-сервера мне нужно проверить размер физического сектора жесткого диска, на котором находится приложение. Для этого я использую DeviceIoControl с IOCTL_STORAGE_QUERY_PROPERTY запросить StorageAccessAlignmentProperty, Проблема в том, что когда я пытаюсь запустить эти команды с веб-сервера, я получаю сообщение об ошибке "Доступ запрещен".

Как я могу получить размер физического сектора жесткого диска, на котором находится inetpub, из приложения веб-сервера??

Из https://msdn.microsoft.com/windows/compatibility/advanced-format-disk-compatibility-update я знаю, что в Windows 8 Microsoft представила новый API, который позволяет осуществлять вызовы из непривилегированного приложения. API в форме нового информационного класса FileFsSectorSizeInformation со связанной структурой FILE_FS_SECTOR_SIZE_INFORMATION, но я не знаю, как заставить это работать с Delphi

Это мой настоящий код, который не работает (написанный на Delphi):

{~~~~~~~~~~~~~~~~~~~~~~~~~}
procedure _CheckSectorSize;

type
  STORAGE_PROPERTY_ID  = (StorageDeviceProperty = 0,
                          StorageAdapterProperty,
                          StorageDeviceIdProperty,
                          StorageDeviceUniqueIdProperty,
                          StorageDeviceWriteCacheProperty,
                          StorageMiniportProperty,
                          StorageAccessAlignmentProperty,
                          StorageDeviceSeekPenaltyProperty,
                          StorageDeviceTrimProperty,
                          StorageDeviceWriteAggregationProperty,
                          StorageDeviceDeviceTelemetryProperty,
                          StorageDeviceLBProvisioningProperty,
                          StorageDevicePowerProperty,
                          StorageDeviceCopyOffloadProperty,
                          StorageDeviceResiliencyProperty,
                          StorageDeviceMediumProductType,
                          StorageAdapterCryptoProperty,
                          StorageDeviceIoCapabilityProperty = 48,
                          StorageAdapterProtocolSpecificProperty,
                          StorageDeviceProtocolSpecificProperty,
                          StorageAdapterTemperatureProperty,
                          StorageDeviceTemperatureProperty,
                          StorageAdapterPhysicalTopologyProperty,
                          StorageDevicePhysicalTopologyProperty,
                          StorageDeviceAttributesProperty);
  STORAGE_QUERY_TYPE  = (PropertyStandardQuery = 0,
                         PropertyExistsQuery = 1,
                         PropertyMaskQuery = 2,
                         PropertyQueryMaxDefined = 3);
  _STORAGE_PROPERTY_QUERY = packed record
    PropertyId: STORAGE_PROPERTY_ID;
    QueryType: STORAGE_QUERY_TYPE;
    AdditionalParameters: array[0..9] of Byte;
 end;
  _STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR = packed record
    Version: DWORD; // Contains the size of this structure, in bytes. The value of this member will change as members are added to the structure.
    Size: DWORD; // Specifies the total size of the data returned, in bytes. This may include data that follows this structure.
    BytesPerCacheLine: DWORD; // The number of bytes in a cache line of the device.
    BytesOffsetForCacheAlignment: DWORD; // The address offset necessary for proper cache access alignment, in bytes.
    BytesPerLogicalSector: DWORD; // The number of bytes in a logical sector of the device.
    BytesPerPhysicalSector: DWORD; // The number of bytes in a physical sector of the device.
    BytesOffsetForSectorAlignment: DWORD; // The logical sector offset within the first physical sector where the first logical sector is placed, in bytes.
 end;

var
  aVolumePath: array[0..MAX_PATH] of AnsiChar;
  aVolumeName: array[0..MAX_PATH] of AnsiChar;
  hFile: THANDLE;
  inbuf: _STORAGE_PROPERTY_QUERY;
  outbuf: _STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR;
  dwLen: DWORD;
  i: integer;

begin

  // Convert the directory to a Volume Name
  aVolumePath[0] := #$00;
  if not GetVolumePathNameA(pAnsiChar(DFRooter_HayStackDirectory),  // _In_  LPCTSTR lpszFileName,
                            aVolumePath,  // _Out_ LPTSTR  lpszVolumePathName,
                            length(aVolumePath)) then raiseLastOsError; // _In_ DWORD cchBufferLength
  aVolumeName[0] := #$00;
  if not GetVolumeNameForVolumeMountPointA(aVolumePath, // _In_  LPCTSTR lpszVolumeMountPoint,
                                           aVolumeName,  // _Out_ LPTSTR lpszVolumeName,
                                           length(aVolumeName)) then raiseLastOsError; // _In_  DWORD   cchBufferLength

  // Opening a physical device so no trailing '\'. Trailing '\' would open the ROOT DIR instead of the volume
  for i := 1 to High(aVolumeName) do
    if aVolumeName[i] = #0 then begin
      if aVolumeName[i-1] = '\' then aVolumeName[i-1] := #0;
      break;
    end;

  //create the file
  hFile := CreateFileA(PAnsiChar(@aVolumeName[0]), // _In_ LPCTSTR lpFileName,
                       GENERIC_READ, // _In_ DWORD dwDesiredAccess,
                       FILE_SHARE_READ or FILE_SHARE_WRITE, //_In_ DWORD dwShareMode,
                       0, // _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                       OPEN_EXISTING, // _In_ DWORD dwCreationDisposition,
                       FILE_ATTRIBUTE_NORMAL, // _In_ DWORD dwFlagsAndAttributes,
                       0); // _In_opt_ HANDLE hTemplateFile
  if (hFile = INVALID_HANDLE_VALUE) then raiseLastOsError;
  try

    ZeroMemory(@inbuf, SizeOf(inbuf));
    ZeroMemory(@outbuf, SizeOf(outbuf));
    inbuf.QueryType := PropertyStandardQuery;
    inbuf.PropertyId := StorageAccessAlignmentProperty;
    outbuf.Size := sizeOf(outbuf);
    if not DeviceIoControl(hFile, //  _In_ HANDLE hDevice,
                           IOCTL_STORAGE_QUERY_PROPERTY, // _In_ DWORD dwIoControlCode,
                           @inbuf, // _In_opt_ LPVOID lpInBuffer,
                           sizeof(inbuf), // _In_ DWORD nInBufferSize,
                           @outbuf, // _Out_opt_ LPVOID lpOutBuffer,
                           sizeof(outbuf), // _In_ DWORD nOutBufferSize,
                           dwLen, // _Out_opt_ LPDWORD lpBytesReturned,
                           nil) then raiseLastOsError; // _Inout_opt_ LPOVERLAPPED lpOverlapped

  finally
    CloseHandle(hFile);
  end;

end;

1 ответ

Решение

Давай поищем IOCTL_STORAGE_QUERY_PROPERTY определение:

CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS) - FILE_ANY_ACCESS используется здесь. это означает, что любой дескриптор файла с любыми правами доступа подходит для этого IOCTL. а как открыть устройство для отправки этого ioctl? ты используешь GENERIC_READ в вызове CreateFileA (и почему бы нет CreateFileW?!). именно в этот момент, я думаю, вы получили ошибку доступа отказано. Также для получения размера сектора вы можете использовать, скажем, IOCTL_DISK_GET_DRIVE_GEOMETRY - это также использовать FILE_ANY_ACCESS, Итак, если у вас есть именно имя устройства, вы можете использовать следующий код (c / C++):

HANDLE hFile = CreateFileW(DeviceName, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);

if (hFile != INVALID_HANDLE_VALUE)
{
    DISK_GEOMETRY dg;
    STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR sad;

    static STORAGE_PROPERTY_QUERY spq = { StorageAccessAlignmentProperty, PropertyStandardQuery }; 
    ULONG BytesReturned;

    if (!DeviceIoControl(hFile, IOCTL_STORAGE_QUERY_PROPERTY, &spq, sizeof(spq), &sad, sizeof(sad), &BytesReturned, 0))
    {
        GetLastError();
    }

    if (!DeviceIoControl(hFile, IOCTL_DISK_GET_DRIVE_GEOMETRY, 0, 0, &dg, sizeof(dg), &BytesReturned, 0))
    {
        GetLastError();
    }

    CloseHandle(hFile);
}
else
{
    GetLastError();
}

этот код отлично работает даже с низким уровнем целостности процесса. никаких привилегий или администратор sid для этого не требуется.

Обратите внимание, что DeviceName должно быть точно именем устройства, а не именем файла / папки.

это среднее имя как "\\\\?\\c:" это хорошо, но для имени "\\\\?\\c:\\" или же "\\\\?\\c:\\anypath" ты уже получил ERROR_INVALID_PARAMETER (или же STATUS_INVALID_PARAMETER), если диск смонтирован файловой системой. это потому что IOCTL_STORAGE_QUERY_PROPERTY или же IOCTL_DISK_GET_DRIVE_GEOMETRY обрабатывается только объектом дискового устройства. но когда диск монтируется файловой системой - запрос подсистемы io перенаправить объект устройства файловой системы вместо этого через VPB (если вы не открываете файл точно по имени устройства и с очень низкими правами доступа). устройство файловой системы просто вернуть STATUS_INVALID_PARAMETER на любом IOCTL (IRP_MJ_DEVICE_CONTROL) если это не открытый том, а файл или каталог. в противном случае он передает его объекту на диске (не путайте его с FSCTL (IRP_MJ_FILE_SYSTEM_CONTROL) - DeviceIoControl внутренний звонок или ZwDeviceIoControlFile (отправить ioctl) или ZwFsControlFile (отправьте fsctl))

Другой вариант - получить информацию о секторе диска - запросить файловую систему об этом, конечно, если диск смонтирован какой-либо файловой системой. мы можем использовать для этого NtQueryVolumeInformationFileFileFsSectorSizeInformation (начать с win8) или FileFsSizeInformation, еще раз - для этого запроса мы можем открыть дескриптор файла с любым доступом. нам не нужно иметь GENERIC_READ

HANDLE hFile = CreateFileW(FileName, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);

if (hFile != INVALID_HANDLE_VALUE)
{
    FILE_FS_SECTOR_SIZE_INFORMATION ffssi;
    FILE_FS_SIZE_INFORMATION ffsi;

    IO_STATUS_BLOCK iosb;

    NtQueryVolumeInformationFile(hFile, &iosb, &ffsi, sizeof(ffsi), FileFsSizeInformation);
    NtQueryVolumeInformationFile(hFile, &iosb, &ffssi, sizeof(ffssi), FileFsSectorSizeInformation);
    CloseHandle(hFile);

}

обратите внимание - здесь мы можем использовать любой путь к файлу и точно такой же путь к устройству (с одним важным примечанием) - так "\\\\?\\c:" а также "\\\\?\\c:\\" и скажи "\\\\?\\c:\\windows\\notepad.exe" - все будет хорошо здесь. однако в случае, если именно имя устройства ("\\\\?\\c:") тебе нужно использовать скажем FILE_EXECUTE доступ к устройству в вызове CreateFileW В противном случае вместо файловой системы устройства будет открыто дисковое устройство и FO_DIRECT_DEVICE_OPEN будет установлен в объекте файла. в результате запрос будет отправлен на устройство диска, которое не обработало его, и вы получили STATUS_INVALID_DEVICE_REQUEST


Фанни, что MSDN говорят

Используя это (IOCTL_STORAGE_QUERY_PROPERTY) IOCTL для получения размера физического сектора имеет несколько ограничений. Это:

  • Требует повышенных привилегий; если ваше приложение не запускается с привилегиями, вам может потребоваться написать приложение службы Windows как
    отмечено выше

это ошибка или сознательная ложь - опять же не нужно никаких привилегий для этого. этот код работал даже с гостевой учетной записи с низким уровнем целостности. мы можем, конечно, использовать и STANDARD_RIGHTS_READ (обратите внимание - это не GENERIC_READ - использовать GENERIC_READ критическая ошибка здесь) в вызове CreateFileW, но можно использовать и 0 (в этом случае CreateFile на самом деле использовать FILE_READ_ATTRIBUTES | SYNCHRONIZE запрос доступа). поэтому документация плохая и неправильная

Я знаю, что на данный момент ему исполнилось два года, но я сам некоторое время боролся с этим сегодня и не был удовлетворен никакими ответами, которые мог найти, из-за их сложности / неполноты. Возможно, этот ответ избавит других от неприятностей.

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

  1. Получить путь к тому файлу в расположении - GetVolumePathName
  2. Откройте том - CreateFile
  3. Получить экстенты тома - VOLUME_DISK_EXTENTS (возможно, придется вызывать дважды)
  4. Для каждого объема тома...
    1. Откройте связанное устройство - CreateFile
    2. Получите дескриптор выравнивания - STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR
  5. Объединить дескрипторы выравнивания из всех экстентов

Это большая проблема для приложений, разработанных для Windows 8 и новее, когда доступен новый класс информации о файлах, который эффективно выполняет все это за следующие два шага:

  1. Откройте файл в расположении - CreateFile
  2. Получить информацию о хранилище файла - GetFileInformationByHandleEx(FileStorageInfo)

Это даст всю необходимую информацию о размерах секторов и их выравнивании независимо от технологии базового устройства в виде структуры FILE_STORAGE_INFO, имеющей следующее определение:

typedef struct _FILE_STORAGE_INFO {
    ULONG LogicalBytesPerSector;
    ULONG PhysicalBytesPerSectorForAtomicity;
    ULONG PhysicalBytesPerSectorForPerformance;
    ULONG FileSystemEffectivePhysicalBytesPerSectorForAtomicity;
    ULONG Flags;
    ULONG ByteOffsetForSectorAlignment;
    ULONG ByteOffsetForPartitionAlignment;
} FILE_STORAGE_INFO, *PFILE_STORAGE_INFO;
Другие вопросы по тегам