当我尝试通过 DeviceIoControl 获取物理扇区大小时,我收到访问被拒绝

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

我需要从我的网络服务器应用程序检查应用程序所在的硬盘驱动器的物理扇区大小。为此,我使用 DeviceIoControlIOCTL_STORAGE_QUERY_PROPERTY 来查询 StorageAccessAlignmentProperty。问题是,当我尝试从网络服务器 运行 这些命令时,出现“访问被拒绝”错误。

如何从网络服务器应用程序检索 inetpub 所在硬盘驱动器的物理扇区大小?

我从 https://msdn.microsoft.com/windows/compatibility/advanced-format-disk-compatibility-update 了解到,在 Windows 8 中,Microsoft 引入了一个新的 API,可以从非特权应用程序调用。 API 的形式是新信息 class 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] := #[=10=];
  if not GetVolumePathNameA(pAnsiChar(DFRooter_HayStackDirectory),  // _In_  LPCTSTR lpszFileName,
                            aVolumePath,  // _Out_ LPTSTR  lpszVolumePathName,
                            length(aVolumePath)) then raiseLastOsError; // _In_ DWORD cchBufferLength
  aVolumeName[0] := #[=10=];
  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;

让我们寻找 IOCTL_STORAGE_QUERY_PROPERTY 定义:

CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS) - FILE_ANY_ACCESS 这里使用。这意味着 any 文件句柄,具有 any 访问权限对于此 IOCTL 是可以的。但是你如何打开设备发送这个 ioctl ?您在调用 CreateFileA 中使用 GENERIC_READ(为什么不使用 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 必须 恰好是 设备名称,而不是 file/folder 名称。

"\\?\c:" 这样的平均名称是可以的,但是对于名称 "\\?\c:\""\\?\c:\anypath" 你已经有 ERROR_INVALID_PARAMETER (或 STATUS_INVALID_PARAMETER),如果磁盘是由文件系统挂载。这是因为 IOCTL_STORAGE_QUERY_PROPERTYIOCTL_DISK_GET_DRIVE_GEOMETRY 仅由磁盘设备对象处理。但是当文件系统安装磁盘时 - io 子系统将请求重定向到文件系统设备对象,而不是通过 VPB (unless you open file by exactly device name and with very low access rights ). file system device just return STATUS_INVALID_PARAMETER 在任何 IOCTL (IRP_MJ_DEVICE_CONTROL) 上,如果这不是卷打开,但是文件或目录。否则它将它传递给磁盘设备对象(不要将其与 FSCTL (IRP_MJ_FILE_SYSTEM_CONTROL) 混淆 - DeviceIoControl 内部调用或 ZwDeviceIoControlFile (发送 ioctl)或 ZwFsControlFile(发送fsctl))

另一个选项,获取磁盘扇区信息 - 查询文件系统,当然以防磁盘被某些文件系统挂载。我们可以为此使用 NtQueryVolumeInformationFile FileFsSectorSizeInformation (begin from win8) or FileFsSizeInformation。再次 - 对于此请求,我们可以使用 any 访问权限打开文件句柄。我们不需要 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:")完全正确,您需要在调用 CreateFileW 中使用说 FILE_EXECUTE 访问设备,否则文件系统设备将打开磁盘设备,而 FO_DIRECT_DEVICE_OPEN 将在文件对象中设置。结果请求将被发送到磁盘设备对象,它不处理它,你得到 STATUS_INVALID_DEVICE_REQUEST


范妮 msdn

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 code 甚至在完整性级别较低的来宾帐户中也能正常工作。我们当然可以在调用 CreateFileW 中使用 and STANDARD_RIGHTS_READ(注意 - 这不是 GENERIC_READ - 这里使用 GENERIC_READ 是严重错误)但可以使用 and 0(在此case CreateFile 实际上使用 FILE_READ_ATTRIBUTES | SYNCHRONIZE 访问请求)。所以文档是错误的

我知道这已经两岁了,但我今天自己也和这个问题打了一段时间,由于它们 complexity/lack 的完整性,我对我能找到的任何答案都不满意。也许这个答案会为其他人省去麻烦。

为了以旧方式获取扇区信息,应用程序必须打开与文件存储位置关联的物理设备。流程简述如下:

  1. 获取位置中文件的卷路径 - GetVolumePathName
  2. 打开音量 - CreateFile
  3. 获取卷范围 - VOLUME_DISK_EXTENTS(可能需要调用两次)
  4. 对于每个卷范围...
    1. 打开关联设备 - CreateFile
    2. 获取对齐描述符 - STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR
  5. 合并来自所有范围的对齐描述符

当有可用的新文件信息 class 可以通过以下两个步骤有效地完成所有这些操作时,这对于为 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;