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.
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
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:
Pour référence, HidpRegisterDeviceInterface de 1709 build
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
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));