Когда я пытаюсь получить размер физического сектора через 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;

person zeus    schedule 17.02.2018    source источник
comment
для IOCTL_STORAGE_QUERY_PROPERTY не нужны никакие привилегии. он работает нормально даже из процесса с низкой целостностью. также вы можете использовать, скажем, IOCTL_DISK_GET_DRIVE_GEOMETRY - также принимать любой дескриптор диска и работать с процессом с низкой целостностью   -  person RbMm    schedule 17.02.2018
comment
и вам не нужен GENERIC_READ доступ. используйте 0 здесь   -  person RbMm    schedule 17.02.2018
comment
Вы уверены, что хотите предоставить потенциальным злоумышленникам дополнительную информацию, чтобы облегчить им успешное проникновение в вашу систему?   -  person IInspectable    schedule 17.02.2018
comment
@rbMn: нет, мне нужны привилегии :( здесь даже напишите msdn .microsoft.com/windows/compatibility/ : Использование этого IOCTL для получения размера физического сектора имеет несколько ограничений: Требуются повышенные привилегии;   -  person zeus    schedule 17.02.2018
comment
@loki - нет, вам не нужны никакие привилегии - я проверяю это даже из процесса с низкой целостностью, и все в порядке. ваша ошибка с использованием GENERIC_READ, который здесь не нужен   -  person RbMm    schedule 17.02.2018


Ответы (2)


поищем 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))

другой вариант, получить информацию о секторе диска - запросить об этом файловую систему, конечно, если диск смонтирован какой-то файловой системой. мы можем использовать для этого NtQueryVolumeInformationFile FileFsSectorSizeInformation< /a> (начиная с 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 запрос доступа). так что документация плохая и неправильная

person RbMm    schedule 17.02.2018
comment
спасибо @RbMn за это очень четкое объяснение! Я не понимал, что это была проблема GENERIC_READ! - person zeus; 17.02.2018
comment
да, это именно проблема - не админы обычно имеют только FILE_READ_ATTRIBUTES|FILE_EXECUTE|SYNCHRONIZE|STANDARD_RIGHTS_READ, когда GENERIC_READ включают, например, FILE_READ_DATA, которых у вас нет. но с другой стороны вам не нужно FILE_READ_DATA. здесь подходит любой допустимый дескриптор файла. лучше всего использовать только SYNCHRONIZE (поэтому 0 в вызове CreateFileW` - person RbMm; 17.02.2018
comment
@RbMm, для 0 желаемого доступа CreateFileW фактически использует FILE_READ_ATTRIBUTES | SYNCHRONIZE, даже если используется FILE_FLAG_OVERLAPPED. Но это не проблема, поскольку каждому предоставляется базовый доступ к дисковому устройству, включая право на синхронизацию, выполнение и чтение метаданных (атрибуты, безопасность). - person Eryk Sun; 18.02.2018
comment
@eryksun - да, ты прав. Я путаю здесь эффект FILE_FLAG_OVERLAPPED, который фактически влияет на параметры создания файла (удалите FILE_SYNCHRONOUS_IO_[NON]ALERT). поэтому, когда мы хотим точно контролировать запрошенный доступ, используйте NtOpenFile или NtCreateFile - person RbMm; 18.02.2018

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

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

  1. Получите путь к тому для файла в расположении - ПолучитьПутьОбъема
  2. Откройте том — CreateFile
  3. Получите экстенты тома — VOLUME_DISK_EXTENTS (возможно, придется вызывать дважды)
  4. For each volume extent...
    1. Open the associated device - 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;
person 240DL    schedule 26.02.2020