Устройство CreateFile через USB HID не работает с Access Denied (5) начиная с Windows 10 1809 - PullRequest
0 голосов
/ 13 декабря 2018

Со времени последнего обновления Windows 10 1809 мы больше не можем открывать наше устройство, похожее на USB HID, используя клавиатуру CreateFile.Мы сократили проблему до этого минимального примера:

#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, а затемвызов HidD_GetAttributes.

. Этот код работает без проблем в предыдущих версиях Windows (проверено в Windows 7, Windows 10 1709 и 1803, а исходный код, из которого он был извлечен, всегда работает с XP и далее), нос последним обновлением (1809) все клавиатура устройства (включая наше) не могут быть открыты, так как 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, является ли она преднамеренной и действительно ли этот обходной путь является правильным.

Ответы [ 2 ]

0 голосов
/ 01 марта 2019

Исправление в этом обновлении Windows, выпущенном сегодня (1 марта 2019 г.).

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

0 голосов
/ 13 декабря 2018

Я думаю, что это новое ограничение безопасности в последней версии Windows 10.

Я искал строку KBD (в формате UTF-16) - она ​​существует только в двух драйверах в версии 1809, hidclass.sys и kbdhid.sys и не существует в версии 1709.

В hidclass.sys они изменили функцию HidpRegisterDeviceInterface.Перед этим выпуском он вызывал IoRegisterDeviceInterface с GUID_DEVINTERFACE_HID и указателем ReferenceString , установленным на 0. Но в новой версии, в зависимости от результата GetHidClassCollection, он передает KBD как ReferenceString указатель.

Внутри kbdhid.sys они изменились KbdHid_Create, и вот проверка для KBDстрока для возврата ошибок (отказ в доступе или нарушение совместного доступа).

Чтобы более точно понять, почему, необходимы дополнительные исследования.Некоторая беда:

enter image description here enter image description here enter image description here


Для справки, HidpRegisterDeviceInterface from1709 build

enter image description here

здесь ReferenceString == 0 всегда ( xor r8d, r8d ), и естьнет проверки cmp word [rbp + a],6 на данных коллекции классов


Однако, KbdHid_Create в 1809 содержит ошибку.Код:

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;
        }
    }
    // ...
}

Что эта функция пытается здесь делать?Он ищет переданное PFILE_OBJECT FileObject из Irp текущего местоположения стека.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 здесь

enter image description here

Еще одно изменение было в HidpRegisterDeviceInterface - перед вызовом IoRegisterDeviceInterface на устройстве оно запрашивает результат GetHidClassCollection и, если какое-то поле WORD (2 байта) в структуреравно 6, добавляет суффикс KBD ( ReferenceString ).Я предполагаю (но я не уверен), что 6 может быть ID использования для клавиатуры , и обоснование для этого префикса состоит в том, чтобы установить Эксклюзивный режим доступа


На самом деле, мыможет начинаться с FileName без \, если мы используем относительное устройство, открытое через OBJECT_ATTRIBUTES.Итак, просто для проверки, мы можем сделать это: если имя интерфейса заканчивается на \KBD, сначала откройте файл без этого суффикса (то есть с пустым относительным именем устройства), и это открытие должно работать нормально;затем мы можем попробовать открыть относительный файл с именем KBD - мы должны получить STATUS_SHARING_VIOLATION в 1809 и 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 или нет.


Кроме того, не связано с ошибкой, но как совет: более эффективно использовать CM_Get_Device_Interface_List вместо SetupAPI:

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