web-dev-qa-db-fra.com

CreateFile sur un périphérique HID USB échoue avec Access Denied (5) depuis Windows 10 1809

Depuis la dernière mise à jour de Windows 10 1809, nous ne pouvons plus ouvrir l'un de nos appareils de type clavier USB HID à l'aide de CreateFile. Nous avons réduit le problème à cet exemple minimal:

#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;
}

Il énumère tous les périphériques HID, essayant d'obtenir l'ID fournisseur/ID produit pour chacun en utilisant CreateFile sur le chemin fourni par SetupDiGetDeviceInterfaceDetail puis en appelant HidD_GetAttributes.

Ce code s'exécute sans problème sur les versions précédentes de Windows (testé sur Windows 7, Windows 10 1709 et 1803, et le code d'origine duquel il a été extrait fonctionne depuis toujours à partir de XP à partir de), mais avec le dernière mise à jour (1809) tous les périphériques clavier (y compris le nôtre) ne peuvent pas être ouverts, car CreateFile échoue avec accès refusé (GetLastError() == 5). L'exécution du programme en tant qu'administrateur n'a aucun effet.

En comparant la sortie avant et après la mise à jour, j'ai remarqué que les appareils qui ne peuvent plus être ouverts ont gagné un \kbd Dans le chemin du périphérique, c'est-à-dire ce qui était auparavant

\\?\hid#vid_24d6&pid_8000&mi_00#7&294a3305&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}

maintenant c'est

\\?\hid#vid_24d6&pid_8000&mi_00#7&294a3305&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}\kbd

S'agit-il d'un bug/d'une nouvelle restriction de sécurité dans la dernière version de Windows 10? Ce code était-il toujours faux et fonctionnait-il par hasard auparavant? Cela peut-il être corrigé?


Mise à jour

Comme une tentative désespérée, nous avons essayé de supprimer le \kbd De la chaîne retournée ... et CreateFile fonctionne maintenant! Donc, maintenant nous avons une solution de contournement, mais il serait intéressant de comprendre si c'est un bogue dans SetupDiGetDeviceInterfaceDetail, si c'est intentionnel et si cette solution de contournement est en fait la bonne chose à faire.

11
Matteo Italia

Le correctif se trouve dans cette mise à jour de Windows publiée aujourd'hui (1er mars 2019).

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

5
andrew

Je pense que c'est une nouvelle restriction de sécurité dans la dernière version de Windows 10.

J'ai cherché la chaîne KBD (au format UTF-16) - elle n'existe que dans deux pilotes dans la version 1809, hidclass.sys et kbdhid.sys , et n'existe pas dans la version 1709.

Dans hidclass.sys , ils ont changé la fonction HidpRegisterDeviceInterface. Avant cette version, il appelait IoRegisterDeviceInterface avec GUID_DEVINTERFACE_HID et le pointeur ReferenceString défini sur 0. Mais dans la nouvelle version, en fonction du résultat de GetHidClassCollection, il passe KBD comme pointeur ReferenceString .

A l'intérieur kbdhid.sys ils ont changé KbdHid_Create, et voici une vérification de la chaîne KBD pour renvoyer les erreurs (accès refusé ou violation de partage).

Pour mieux comprendre pourquoi, davantage de recherches sont nécessaires. Certains désastre:

enter image description hereenter image description hereenter image description here


Pour référence, HidpRegisterDeviceInterface de 1709 build

enter image description here

ici ReferenceString == 0 toujours ( xor r8d, r8d ), et il n'y a pas de contrôle cmp Word [rbp + a],6 sur les données de collecte de classe


Pourtant, KbdHid_Create en 1809 contient un bogue. Le code est:

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

Qu'est-ce que cette fonction essaie de faire ici? Il recherche passé PFILE_OBJECT FileObject de Irp l'emplacement actuel de la pile. Si aucun FileObject n'est fourni ou s'il a un nom vide, autorisez open; sinon, l'ouverture échoue.

Avant 1809, il échouait toujours avec l'erreur STATUS_ACCESS_DENIED (0xc0000022), mais à partir de 1809, le nom est vérifié, et s'il est égal à KBD (sensible à la casse) une autre erreur - STATUS_SHARING_VIOLATION est retourné. Cependant, le nom commence toujours par le \, donc il ne correspondra jamais à KBD. Ça peut être \KBD, donc, pour corriger cette vérification, la ligne suivante doit être remplacée par:

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

et effectuer la comparaison avec cette chaîne. Donc, par conception, nous aurions dû avoir un STATUS_SHARING_VIOLATION erreur lors de la tentative d'ouverture d'un clavier via *\KBD nom, mais en raison d'une erreur d'implémentation, nous avons obtenu STATUS_ACCESS_DENIED ici

enter image description here

Une autre modification était dans HidpRegisterDeviceInterface - avant l'appel à IoRegisterDeviceInterface sur le périphérique, il interroge le résultat GetHidClassCollection, et si un champ Word (2 octets) dans la structure est égale à 6, ajoute le suffixe KBD ( ReferenceString ). Je suppose (mais je ne suis pas sûr) que 6 peut être le ID d'utilisation du clavier , et la raison de ce préfixe est de définir le mode d'accès exclusif


En fait, nous pouvons commencer un FileName sans \ si nous utilisons un périphérique relatif ouvert via OBJECT_ATTRIBUTES. Donc, juste pour le test, nous pouvons le faire: si le nom de l'interface se termine par \KBD, ouvrez d'abord le fichier sans ce suffixe (donc avec un nom de périphérique relatif vide), et cette ouverture doit fonctionner correctement; ensuite, nous pouvons essayer un fichier ouvert relatif portant le nom KBD - nous devons obtenir STATUS_SHARING_VIOLATION en 1809 et STATUS_ACCESS_DENIED dans les versions précédentes (mais ici nous n'aurons pas de \KBD suffixe):

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");
    }
}

Lors d'un test en 1809, j'ai eu STATUS_SHARING_VIOLATION, qui montre également un autre bogue dans kbdhid.KbdHid_Create - si nous vérifions FileName, nous devons vérifier RelatedFileObject - est-ce 0 ou non.


Aussi, pas lié au bug, mais comme suggestion: il est plus efficace d'utiliser CM_Get_Device_Interface_List au lieu de 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));
12
RbMm