web-dev-qa-db-ja.com

Windows 10 1809以降、CreateFile over USB HIDデバイスがアクセス拒否(5)で失敗する

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のバグかどうか、意図的なものかどうか、そしてこの回避策が実際に正しいことかどうかを理解することは興味深いでしょう。

13
Matteo Italia

この修正は、本日(2019年3月1日)にリリースされたこのWindowsアップデートにあります。

https://support.Microsoft.com/en-us/help/4482887/windows-10-update-kb4482887

5
andrew

これは最新のWindows 10バージョンの新しいセキュリティ制限だと思います。

文字列KBD(UTF-16形式)を探しました-バージョン1809のhidclass.sysおよびkbdhid.sysの2つのドライバーにのみ存在します、バージョン1709には存在しません。

hidclass.sysでは、HidpRegisterDeviceInterface関数が変更されました。このリリースの前は、 IoRegisterDeviceInterfaceGUID_DEVINTERFACE_HID で呼び出し、ReferenceStringポインタを0に設定していました。ただし、新しいバージョン、GetHidClassCollectionの結果に応じて、KBDReferenceStringポインタとして渡します。

内部kbdhid.sysKbdHid_Createを変更しました。エラーを返すためにKBD文字列をチェックします(アクセス拒否または共有違反)。

理由をより正確に理解するには、より多くの研究が必要です。いくつかの不満:

enter image description hereenter image description hereenter image description here


参考までに、1709ビルドのHidpRegisterDeviceInterface

enter image description here

ここ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_DENIED0xc0000022)で失敗していましたが、1809以降、名前が確認され、KBD(大文字と小文字を区別)と等しい場合は別のエラー-STATUS_SHARING_VIOLATIONが返されます。ただし、名前は常に\記号で始まるため、KBDと一致することはありません。 \KBDになる可能性があるため、このチェックを修正するには、次の行を次のように変更する必要があります。

UNICODE_STRING KBD = RTL_CONSTANT_STRING(L"\\KBD");

この文字列との比較を実行します。したがって、設計上、STATUS_SHARING_VIOLATIONの名前でキーボードデバイスを開こうとすると*\KBDエラーが発生するはずですが、実装エラーのため、実際にはSTATUS_ACCESS_DENIEDがここに表示されます

enter image description here

別の変更は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));
12
RbMm

回避策は 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コンポーネントを再コンパイルします。

0