Ответ 1
Исправление в этом обновлении Windows, выпущенном сегодня (1 марта 2019 г.).
https://support.microsoft.com/en-us/help/4482887/windows-10-update-kb4482887
Со времени последнего обновления 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
, если он намеренный, и если этот обходной путь на самом деле правильный.
Исправление в этом обновлении Windows, выпущенном сегодня (1 марта 2019 г.).
https://support.microsoft.com/en-us/help/4482887/windows-10-update-kb4482887
Я думаю, что это новое ограничение безопасности в последней версии 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
, возвращающей ошибки (отказ в доступе или нарушение совместного доступа).
Чтобы понять, почему, нужно больше исследований. Некоторая беда:
Для справки: HidpRegisterDeviceInterface из сборки 1709 года
здесь 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
здесь
Другое изменение было в HidpRegisterDeviceInterface
- перед вызовом IoRegisterDeviceInterface
на устройстве оно запрашивает результат GetHidClassCollection
, и если какое-то поле WORD
(2 байта) в структуре равно 6, добавляет суффикс KBD
(ReferenceString). Я думаю (но я не уверен), что 6 может быть идентификатором использования для клавиатуры, и обоснование для этого префикса состоит в том, чтобы установить эксклюзивный режим доступа
На самом деле, мы можем начать 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));
Обходной путь можно найти в Delphi-Praxis на немецком языке
Для краткости: изменение в модуле JvHidControllerClass
if not HidD_GetAttributes(HidFileHandle, FAttributes) then
raise EControllerError.CreateRes(@RsEDeviceCannotBeIdentified);
в
HidD_GetAttributes(HidFileHandle, FAttributes);
и перекомпилируйте Delhi JCL и JCVL Components, запустив JEDI Install EXE.