windowsdelphiwinapiiisdeviceiocontrol

When i try to get physical sector size via DeviceIoControl i receive Access is denied


From my webserver app i need to check the physical sector size of the harddrive where the app is located. For this i use DeviceIoControl with IOCTL_STORAGE_QUERY_PROPERTY to query StorageAccessAlignmentProperty. The problem is that when i try to run these commands from the webserver i got "Access is denied" error.

How can i retrieve the physical sector size of the harddrive where inetpub is located from the webserver app ??

I know from https://msdn.microsoft.com/windows/compatibility/advanced-format-disk-compatibility-update that with Windows 8 Microsoft has introduced a new API that enables Calling from an unprivileged app. The API is in the form of a new info class FileFsSectorSizeInformation with associated structure FILE_FS_SECTOR_SIZE_INFORMATION, but i don't know how to make it working with Delphi

This is my actual code that don't work (written in 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;

Solution

  • let look for IOCTL_STORAGE_QUERY_PROPERTY definition:

    CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS) - FILE_ANY_ACCESS used here. this mean that any file handle, with any access rights is ok for this IOCTL. but how you open device for send this ioctl ? you use GENERIC_READ in call CreateFileA (and why not CreateFileW ?!). exactly at this point i guess you got access denied error. also for get sector size you can use say IOCTL_DISK_GET_DRIVE_GEOMETRY - it also use FILE_ANY_ACCESS. so, if you have exactly device name, you can use next code (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();
    }
    

    this code perfectly worked even from low integrity process. no any privileges or administrator sid require for this.

    note that DeviceName must be exactly device name, not file/folder name.

    this mean name like "\\\\?\\c:" is ok but for name "\\\\?\\c:\\" or "\\\\?\\c:\\anypath" you already got ERROR_INVALID_PARAMETER (or STATUS_INVALID_PARAMETER), if disk is mounted by filesystem. this is because IOCTL_STORAGE_QUERY_PROPERTY or IOCTL_DISK_GET_DRIVE_GEOMETRY is handled only by disk device object. but when disk is mounted by filesystem - io subsystem redirect request to filesystem device object instead via VPB (unless you open file by exactly device name and with very low access rights ). file system device just return STATUS_INVALID_PARAMETER on any IOCTL (IRP_MJ_DEVICE_CONTROL) if this is not volume open, but file or directory. otherwise it pass it down to disk device object (not confuse this with FSCTL (IRP_MJ_FILE_SYSTEM_CONTROL) - the DeviceIoControl internal call or ZwDeviceIoControlFile (send ioctl) or ZwFsControlFile (send fsctl))

    another option, get disk sector information - query file system about this, of course in case disk is mounted by some filesystem. we can use for this NtQueryVolumeInformationFile FileFsSectorSizeInformation (begin from win8) or FileFsSizeInformation. again - for this request we can open file handle with any access. we not need have 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);
    
    }
    

    note - that here we can use any file path and exactly device path too (with one important note) - so "\\\\?\\c:" and "\\\\?\\c:\\" and say "\\\\?\\c:\\windows\\notepad.exe" - all will be ok here. however in case exactly device name ("\\\\?\\c:") you need use say FILE_EXECUTE access to device in call CreateFileW, otherwise instead file system device will be opened disk device and FO_DIRECT_DEVICE_OPEN will be set in file object. as result request will be send to disk device object, which not handle it and you got STATUS_INVALID_DEVICE_REQUEST


    fanny that msdn say

    Using this (IOCTL_STORAGE_QUERY_PROPERTY) IOCTL to get the physical sector size does have several limitations. It:

    • Requires elevated privilege; if your app is not running with privilege, you may need to write a Windows Service Application as
      noted above

    this is mistake or conscious lie - again not need any elevated privilege for this. this code worked even from guest account too with low integrity level. we can of course use and STANDARD_RIGHTS_READ (note - this is not GENERIC_READ - use GENERIC_READ is critical error here) in call CreateFileW, but can use and 0 (in this case CreateFile actually use FILE_READ_ATTRIBUTES | SYNCHRONIZE access request). so documentation is bad and wrong