Windows 10 1809の最新のアップデート以降、CreateFile
を使用してUSB HIDキーボードのようなデバイスを開くことができなくなりました。問題を次の最小限の例に減らしました。
_#include <windows.h>
#include <setupapi.h>
#include <stdio.h>
#include <hidsdi.h>
void bad(const char *msg) {
DWORD w = GetLastError();
fprintf(stderr, "bad: %s, GetLastError() == 0x%08x\n", msg, (unsigned)w);
}
int main(void) {
int i;
GUID hidGuid;
HDEVINFO deviceInfoList;
const size_t DEVICE_DETAILS_SIZE = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + MAX_PATH;
SP_DEVICE_INTERFACE_DETAIL_DATA *deviceDetails = alloca(DEVICE_DETAILS_SIZE);
deviceDetails->cbSize = sizeof(*deviceDetails);
HidD_GetHidGuid(&hidGuid);
deviceInfoList = SetupDiGetClassDevs(&hidGuid, NULL, NULL,
DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if(deviceInfoList == INVALID_HANDLE_VALUE) {
bad("SetupDiGetClassDevs");
return 1;
}
for (i = 0; ; ++i) {
SP_DEVICE_INTERFACE_DATA deviceInfo;
DWORD size = DEVICE_DETAILS_SIZE;
HIDD_ATTRIBUTES deviceAttributes;
HANDLE hDev = INVALID_HANDLE_VALUE;
fprintf(stderr, "Trying device %d\n", i);
deviceInfo.cbSize = sizeof(deviceInfo);
if (!SetupDiEnumDeviceInterfaces(deviceInfoList, 0, &hidGuid, i,
&deviceInfo)) {
if (GetLastError() == ERROR_NO_MORE_ITEMS) {
break;
} else {
bad("SetupDiEnumDeviceInterfaces");
continue;
}
}
if(!SetupDiGetDeviceInterfaceDetail(deviceInfoList, &deviceInfo,
deviceDetails, size, &size, NULL)) {
bad("SetupDiGetDeviceInterfaceDetail");
continue;
}
fprintf(stderr, "Opening device %s\n", deviceDetails->DevicePath);
hDev = CreateFile(deviceDetails->DevicePath, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
if(hDev == INVALID_HANDLE_VALUE) {
bad("CreateFile");
continue;
}
deviceAttributes.Size = sizeof(deviceAttributes);
if(HidD_GetAttributes(hDev, &deviceAttributes)) {
fprintf(stderr, "VID = %04x PID = %04x\n", (unsigned)deviceAttributes.VendorID, (unsigned)deviceAttributes.ProductID);
} else {
bad("HidD_GetAttributes");
}
CloseHandle(hDev);
}
SetupDiDestroyDeviceInfoList(deviceInfoList);
return 0;
}
_
すべてのHIDデバイスを列挙し、CreateFile
によって提供されるパスでSetupDiGetDeviceInterfaceDetail
を使用して各デバイスのベンダーID /製品IDを取得し、次に_HidD_GetAttributes
_を呼び出します。
このコードは以前のWindowsバージョン(Windows 7、Windows 10 1709および1803でテスト済み)で問題なく実行されます。これが抽出された元のコードは常にXP以降)から機能しますが、最新の更新(1809)すべてのkeyboardデバイス(当社のものを含む)を開くことができません。CreateFile
がアクセス拒否(GetLastError()
== 5)。管理者としてプログラムを実行しても効果はありません。
更新前後の出力を比較すると、現在開くことができないデバイスは、デバイスパスで末尾の_\kbd
_を取得していることに気付きました。
_\\?\hid#vid_24d6&pid_8000&mi_00#7&294a3305&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
_
今は
_\\?\hid#vid_24d6&pid_8000&mi_00#7&294a3305&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}\kbd
_
最新のWindows 10バージョンのバグ/新しいセキュリティ制限ですか?このコードは常に間違っていましたか?これは修正できますか?
更新
必死の試みとして、返された文字列から_\kbd
_を削除しようとしました...そしてCreateFile
が機能するようになりました!したがって、これで回避策がありますが、それがSetupDiGetDeviceInterfaceDetail
のバグかどうか、意図的なものかどうか、そしてこの回避策が実際に正しいことかどうかを理解することは興味深いでしょう。
この修正は、本日(2019年3月1日)にリリースされたこのWindowsアップデートにあります。
https://support.Microsoft.com/en-us/help/4482887/windows-10-update-kb4482887
これは最新のWindows 10バージョンの新しいセキュリティ制限だと思います。
文字列KBD
(UTF-16形式)を探しました-バージョン1809のhidclass.sysおよびkbdhid.sysの2つのドライバーにのみ存在します、バージョン1709には存在しません。
hidclass.sysでは、HidpRegisterDeviceInterface
関数が変更されました。このリリースの前は、 IoRegisterDeviceInterface
を GUID_DEVINTERFACE_HID
で呼び出し、ReferenceStringポインタを0に設定していました。ただし、新しいバージョン、GetHidClassCollection
の結果に応じて、KBD
をReferenceStringポインタとして渡します。
内部kbdhid.sysはKbdHid_Create
を変更しました。エラーを返すためにKBD
文字列をチェックします(アクセス拒否または共有違反)。
理由をより正確に理解するには、より多くの研究が必要です。いくつかの不満:
参考までに、1709ビルドのHidpRegisterDeviceInterface
ここReferenceString == 0常に(xor r8d、r8d)、そしてクラスコレクションデータのチェックcmp Word [rbp + a],6
はありません
ただし、1809年のKbdHid_Create
にはバグが含まれています。コードは次のとおりです。
NTSTATUS KbdHid_Create(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
//...
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
if (PFILE_OBJECT FileObject = IrpSp->FileObject)
{
PCUNICODE_STRING FileName = &FileObject->FileName;
if (FileName->Length)
{
#if ver == 1809
UNICODE_STRING KBD = RTL_CONSTANT_STRING(L"KBD"); // !! bug !!
NTSTATUS status = RtlEqualUnicodeString(FileName, &KBD, FALSE)
? STATUS_SHARING_VIOLATION : STATUS_ACCESS_DENIED;
#else
NTSTATUS status = STATUS_ACCESS_DENIED;
#endif
// log
Irp->IoStatus.Status = status;
IofCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
}
// ...
}
この関数がここでやろうとしていることは何ですか? Irpから渡されたPFILE_OBJECT FileObject
を現在のスタックの場所から探します。 FileObject
が指定されていないか、名前が空です。開くことができます。そうでない場合、オープンは失敗します。
1809以前は常にエラーSTATUS_ACCESS_DENIED
(0xc0000022
)で失敗していましたが、1809以降、名前が確認され、KBD
(大文字と小文字を区別)と等しい場合は別のエラー-STATUS_SHARING_VIOLATION
が返されます。ただし、名前は常に\
記号で始まるため、KBD
と一致することはありません。 \KBD
になる可能性があるため、このチェックを修正するには、次の行を次のように変更する必要があります。
UNICODE_STRING KBD = RTL_CONSTANT_STRING(L"\\KBD");
この文字列との比較を実行します。したがって、設計上、STATUS_SHARING_VIOLATION
の名前でキーボードデバイスを開こうとすると*\KBD
エラーが発生するはずですが、実装エラーのため、実際にはSTATUS_ACCESS_DENIED
がここに表示されます
別の変更はHidpRegisterDeviceInterface
でした-デバイスでIoRegisterDeviceInterface
を呼び出す前に、GetHidClassCollection
結果をクエリし、Word
(2バイト)フィールドが構造は6に等しく、KBD
サフィックスを追加します(ReferenceString)。 6は キーボードの使用法ID になる可能性があると思います(ただし、よくわかりません)。このプレフィックスの根拠は、排他的アクセスモードを設定することです。
実際、\
を介して相対デバイスを開く場合、OBJECT_ATTRIBUTES
なしでFileNameを開始できます。したがって、テストのためだけにこれを行うことができます。インターフェイス名が\KBD
で終わる場合は、最初にこのサフィックスなしでファイルを開いて(相対デバイス名が空の場合)、このオープンが正常に機能する必要があります。次に、KBD
という名前の相対オープンファイルを試すことができます-1809年にはSTATUS_SHARING_VIOLATION
を取得し、以前のビルドではSTATUS_ACCESS_DENIED
を取得する必要があります(ただし、ここでは\KBD
サフィックスはありません。 ):
void TestOpen(PWSTR pszDeviceInterface)
{
HANDLE hFile;
if (PWSTR c = wcsrchr(pszDeviceInterface, '\\'))
{
static const UNICODE_STRING KBD = RTL_CONSTANT_STRING(L"KBD");
if (!wcscmp(c + 1, KBD.Buffer))
{
*c = 0;
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, const_cast<PUNICODE_STRING>(&KBD) };
oa.RootDirectory = CreateFileW(pszDeviceInterface, 0,
FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
if (oa.RootDirectory != INVALID_HANDLE_VALUE)
{
IO_STATUS_BLOCK iosb;
// will be STATUS_SHARING_VIOLATION (c0000043)
NTSTATUS status = NtOpenFile(&hFile, SYNCHRONIZE, &oa, &iosb,
FILE_SHARE_VALID_FLAGS, FILE_SYNCHRONOUS_IO_NONALERT);
CloseHandle(oa.RootDirectory);
if (0 <= status)
{
PrintAttr(hFile);
CloseHandle(hFile);
}
}
return ;
}
}
hFile = CreateFileW(pszDeviceInterface, 0,
FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
PrintAttr(hFile);
CloseHandle(hFile);
}
}
void PrintAttr(HANDLE hFile)
{
HIDD_ATTRIBUTES deviceAttributes = { sizeof(deviceAttributes) };
if(HidD_GetAttributes(hFile, &deviceAttributes)) {
printf("VID = %04x PID = %04x\r\n",
(ULONG)deviceAttributes.VendorID, (ULONG)deviceAttributes.ProductID);
} else {
bad(L"HidD_GetAttributes");
}
}
1809のテストで実際にSTATUS_SHARING_VIOLATION
を取得しましたが、これはkbdhid.KbdHid_Create
の別のバグも示しています-FileName
をチェックする場合は、チェックする必要があります RelatedFileObject
-0かどうか。
また、バグとは関係ありませんが、提案として:SetupAPIの代わりにCM_Get_Device_Interface_List
を使用する方が効率的です。
volatile UCHAR guz = 0;
CONFIGRET EnumInterfaces(PGUID InterfaceClassGuid)
{
CONFIGRET err;
PVOID stack = alloca(guz);
ULONG BufferLen = 0, NeedLen = 256;
union {
PVOID buf;
PWSTR pszDeviceInterface;
};
for(;;)
{
if (BufferLen < NeedLen)
{
BufferLen = RtlPointerToOffset(buf = alloca((NeedLen - BufferLen) * sizeof(WCHAR)), stack) / sizeof(WCHAR);
}
switch (err = CM_Get_Device_Interface_ListW(InterfaceClassGuid,
0, pszDeviceInterface, BufferLen, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
{
case CR_BUFFER_SMALL:
if (err = CM_Get_Device_Interface_List_SizeW(&NeedLen, InterfaceClassGuid,
0, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
{
default:
return err;
}
continue;
case CR_SUCCESS:
while (*pszDeviceInterface)
{
TestOpen(pszDeviceInterface);
pszDeviceInterface += 1 + wcslen(pszDeviceInterface);
}
return 0;
}
}
}
EnumInterfaces(const_cast<PGUID>(&GUID_DEVINTERFACE_HID));
回避策は Delphi-Praxis in German にあります。
略して、ユニットJvHidControllerClassの変更
if not HidD_GetAttributes(HidFileHandle, FAttributes) then
raise EControllerError.CreateRes(@RsEDeviceCannotBeIdentified);
に
HidD_GetAttributes(HidFileHandle, FAttributes);
そして、JEDIインストールEXEを実行して、Delhi JCLおよびJCVLコンポーネントを再コンパイルします。