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;
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