Когда я пытаюсь получить размер физического сектора через 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))
Другой вариант - получить информацию о секторе диска - запросить файловую систему об этом, конечно, если диск смонтирован какой-либо файловой системой. мы можем использовать для этого NtQueryVolumeInformationFile
FileFsSectorSizeInformation
(начать с 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
запрос доступа). поэтому документация плохая и неправильная
Я знаю, что на данный момент ему исполнилось два года, но я сам некоторое время боролся с этим сегодня и не был удовлетворен никакими ответами, которые мог найти, из-за их сложности / неполноты. Возможно, этот ответ избавит других от неприятностей.
Чтобы получить информацию о секторе старым способом, приложение должно открыть физическое устройство, связанное с местом, где хранится файл. Вкратце процесс выглядит следующим образом:
- Получить путь к тому файлу в расположении - GetVolumePathName
- Откройте том - CreateFile
- Получить экстенты тома - VOLUME_DISK_EXTENTS (возможно, придется вызывать дважды)
- Для каждого объема тома...
- Откройте связанное устройство - CreateFile
- Получите дескриптор выравнивания - STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR
- Объединить дескрипторы выравнивания из всех экстентов
Это большая проблема для приложений, разработанных для Windows 8 и новее, когда доступен новый класс информации о файлах, который эффективно выполняет все это за следующие два шага:
- Откройте файл в расположении - CreateFile
- Получить информацию о хранилище файла - 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;