0

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;
zeus
  • 12,173
  • 9
  • 63
  • 184
  • for `IOCTL_STORAGE_QUERY_PROPERTY` not need any privileges. it work ok even from low integrity process. also you can use say `IOCTL_DISK_GET_DRIVE_GEOMETRY` - also accept any disk handle and work from low integrity process – RbMm Feb 17 '18 at 09:45
  • and you not need `GENERIC_READ` access. use 0 here – RbMm Feb 17 '18 at 09:46
  • Are you sure you want to expose additional information to potential attackers, making it easier for them to successfully penetrate your system? – IInspectable Feb 17 '18 at 10:20
  • @rbMn : no I need privilege :( it's even write here https://msdn.microsoft.com/windows/compatibility/advanced-format-disk-compatibility-update : Using this IOCTL to get the physical sector size does have several limitations. It: Requires elevated privilege; – zeus Feb 17 '18 at 11:08
  • @loki - no, you **not need** any privilege - i check this even from low integrity process and all ok. your error by using `GENERIC_READ` which is not need here – RbMm Feb 17 '18 at 11:14

2 Answers2

6

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

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • thanks @RbMn for this very clear explanation ! I was not realizing that it's was GENERIC_READ the problem ! – zeus Feb 17 '18 at 13:22
  • yes, this is exactly problem - not admin usually have only `FILE_READ_ATTRIBUTES|FILE_EXECUTE|SYNCHRONIZE|STANDARD_RIGHTS_READ` when `GENERIC_READ` include for example `FILE_READ_DATA` which you not have. but from another side you not need `FILE_READ_DATA`. any valid file handle is ok here. the best use `SYNCHRONIZE` only (so 0 in call CreateFileW` – RbMm Feb 17 '18 at 13:32
  • 2
    @RbMm, for 0 desired access `CreateFileW` actually uses `FILE_READ_ATTRIBUTES | SYNCHRONIZE`, even if `FILE_FLAG_OVERLAPPED` is used. But this isn't a problem since everyone is granted basic access to the disk device, including the right to synchronize, execute, and read meta-data (attributes, security). – Eryk Sun Feb 18 '18 at 00:50
  • @eryksun - yes, you correct. I confuse effect of `FILE_FLAG_OVERLAPPED` here which actually affect file creation options (remove `FILE_SYNCHRONOUS_IO_[NON]ALERT`). so when we want exactly control requested access use `NtOpenFile` or `NtCreateFile` – RbMm Feb 18 '18 at 01:04
1

I know this is two years old at this point, but I fought with this one today for a while myself and wasn't satisfied with any answers I could find due to their complexity/lack of completeness. Perhaps this answer will save trouble for others.

In order to get the sector information the old way, the application must open the physical device associated with the location in which the file is stored. The process in a nutshell is as follows:

  1. Get the volume path for a file in the location - GetVolumePathName
  2. Open the volume - CreateFile
  3. Get the volume extents - VOLUME_DISK_EXTENTS (may have to be called twice)
  4. For each volume extent...
    1. Open the associated device - CreateFile
    2. Get the alignment descriptor - STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR
  5. Merge the alignment descriptors from all extents

This is a good deal of trouble for applications designed for Windows 8 and newer when there is a new file information class available that effectively does all of this in the following two steps:

  1. Open a file in the location - CreateFile
  2. Get the file's storage information - GetFileInformationByHandleEx(FileStorageInfo)

This will yield all the relevant information one could want about sector sizes and alignment regardless of the underlying device technology in the form of a FILE_STORAGE_INFO structure having the following definition:

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;
240DL
  • 17
  • 5