[WDF] Getting USB Device Descriptor (in upperfilter driver)

I have a keyboard UpperFilter driver (that supports both PS/2 and HID/USB keyboards) and I’m trying to use as much info I can get about the devices as possible to identify them. For PS/2 I’m just using the HardwareID, but for USB keyboards I want to also use the Serial Number (if provided, as most manufacturers don’t). I see that the easiest way is to get it using WdfUsbTargetDeviceGetDeviceDescriptor, however for that I need to create an WDFUSBDEVICE. I’m trying to useWdfUsbTargetDeviceCreateWithParametersto create an WDFUSBDEVICE, however the function fails with code 0xC0000010 (STATUS_INVALID_DEVICE_REQUEST). The docs of the method says it can be used in EvtDeviceAdd (and that is where I first tried to use it), however the docs for WdfUsbTargetDeviceCreate says it can only be used in EvtDevicePrepareHardware, so I moved my method there thinking this may be the reason. However, I also get the same status here. The device is a normal USB keyboard, the machine is a VM running inside VMWare with the keyboard added to vmware usb passtrough functionality. This is my code:

		WDF_USB_DEVICE_CREATE_CONFIG UsbDeviceConfig;

		// Create WDFUSBDEVICE object
		WDF_USB_DEVICE_CREATE_CONFIG_INIT(&UsbDeviceConfig, USBD_CLIENT_CONTRACT_VERSION_602);

		NTSTATUS UsbDeviceCreateStatus = WdfUsbTargetDeviceCreateWithParameters(hDevice, &UsbDeviceConfig, WDF_NO_OBJECT_ATTRIBUTES, &filterExt->UsbDevice);
		if (!NT_SUCCESS(UsbDeviceCreateStatus)) {
			KdPrint(("WdfUsbTargetDeviceCreateWithParameters failed with status 0x%x\n", UsbDeviceCreateStatus));

			// If failed to create an USB Device, handle device as non-usb (PS/2)
			filterExt->UsbDevice = NULL;
		}

I can’t seem to find an explanation of why the device creation would fail except vmware usb passthrough, however I do not have any testing physical PC on which I could test to see if this is the problem, so before finding a spare harddrive to put a copy of windows on it and to test it on my own PC, I thought it’s better to ask here as the problem may be another one that I do not see and someone else could point it to me.

This is a KMDF driver… not a UMDF driver?

Enable WDF Verifier, set Verbose logging, and dump the log with !WDFKD.WDFLOGDUMP

P

1 Like

@“Peter_Viscarola_(OSR)” said:
This is a KMDF driver… not a UMDF driver?

Enable WDF Verifier, set Verbose logging, and dump the log with !WDFKD.WDFLOGDUMP

P

Yes, it is a KMDF driver (it’s a upper class filter for keyboards and as per-microsoft documentation, UMDF can’t be used to write a class filter driver).

Log dumped using wdflogdump(posted it on pastebin because it wouldn’t let me to put it here as it is too long): https://pastebin.com/TZ7aEapd

Kbdhid as the FDO does not accept USB IO requests. It only accepts HID and kbdclass requests. And even if you sent the request directly to the PDO it would not work as it’s a HID PDO, not a USB enumerated PDO. You can’t get a USB descriptor from where you are in the stack.

1 Like

@Doron_Holan said:
Kbdhid as the FDO does not accept USB IO requests. It only accepts HID and kbdclass requests. And even if you sent the request directly to the PDO it would not work as it’s a HID PDO, not a USB enumerated PDO. You can’t get a USB descriptor from where you are in the stack.

So there is no method I can get the SerialNumber (if the device has one) from here? You’re saying that it accepts HID requests, does that mean I can use WdfRequestSend to send an IOCTL_HID_GET_SERIALNUMBER_STRING request down and hopefully getting a S/N for the device (I guess there is no point in sending an IOCTL_INTERNAL_USB_SUBMIT_URB to get an usb device descriptor, as you said it doesn’t accepts USB IO requests)?

I misspoke slightly. kbdhid doesn’t allow incoming HID requests either, only keyboard class IOCTLs are processed. Technically you could send IOCTL_HID_GET_SERIALNUMBER_STRING directly to the PDO, but if HIDCLASS requires that you have a valid file object to send this IOCTL, you won’t be able to open that file object. But perhaps this is all too low level. Why not query for the DEVICE_CAPABILITIES, see if UniqueID is set to TRUE. If it is, query the device instance ID and you will get your unique identifier.

1 Like

@Doron_Holan said:
I misspoke slightly. kbdhid doesn’t allow incoming HID requests either, only keyboard class IOCTLs are processed. Technically you could send IOCTL_HID_GET_SERIALNUMBER_STRING directly to the PDO, but if HIDCLASS requires that you have a valid file object to send this IOCTL, you won’t be able to open that file object. But perhaps this is all too low level. Why not query for the DEVICE_CAPABILITIES, see if UniqueID is set to TRUE. If it is, query the device instance ID and you will get your unique identifier.

Thanks for your info and help. When querying for instance ID (IRP_MN_QUERY_ID) when UniqueID is set to FALSE, isn’t the OS supposed to give a unique ID based on the current usb port the device is plugged in? Because when the UniqueID is FALSE, what I get from IRP_MN_QUERY_ID is (null). I haven’t yet tested with a keyboard that has the UniqueID set to TRUE because I’m still searching for one that does.

I have this to get and print the result of the IRP:

// print device capabilities
[...]
WDF_REQUEST_COMPLETION_PARAMS completionParams;
WDF_REQUEST_COMPLETION_PARAMS_INIT(&completionParams);
WdfRequestGetCompletionParams(Request, &completionParams);

KdPrint(("Instance get status: 0x%x, Instance id: %ws\n", completionParams.IoStatus.Status, completionParams.IoStatus.Information));

And this is printed:

dev_cap: Address 1, SurpriseRemoval: TRUE, Unique ID: FALSE 
Instance get status: 0x0, Instance id: (null)

As you see, the IRP finish successfully (if I do the same IRP for a PS/2 keyboard, it will return with an error status code and not with 0x0), however the instance id is printed as being (null). Why does this happen? Does InstanceID only gets generated when there are two devices of the same type plugged in and may this be the reason I’m getting it as null?

Post the code that queries the ID, perhaps there is a mistake

@Doron_Holan said:
Post the code that queries the ID, perhaps there is a mistake

		// Instance id
		{
			WDFREQUEST Request;
			WDF_REQUEST_SEND_OPTIONS options;
			WDF_REQUEST_SEND_OPTIONS_INIT(&options, WDF_REQUEST_SEND_OPTION_SYNCHRONOUS);

			WDFIOTARGET target = WdfDeviceGetIoTarget(s->WdfDevice);

			NTSTATUS instStatus;

			IO_STACK_LOCATION stack;
			RtlZeroMemory(&stack, sizeof(stack));

			stack.MajorFunction = IRP_MJ_PNP;
			stack.MinorFunction = IRP_MN_QUERY_ID;
			stack.Parameters.QueryId.IdType = BusQueryInstanceID;

			instStatus = WdfRequestCreate(WDF_NO_OBJECT_ATTRIBUTES, target, &Request);
			if (NT_SUCCESS(instStatus)) {

				WDF_REQUEST_REUSE_PARAMS reuse;
				WDF_REQUEST_REUSE_PARAMS_INIT(&reuse, WDF_REQUEST_REUSE_NO_FLAGS, STATUS_NOT_SUPPORTED);

				WdfRequestReuse(Request, &reuse);

				WdfRequestWdmFormatUsingStackLocation(Request, &stack);

				if (WdfRequestSend(Request, target, &options) == TRUE) {
					instStatus = WdfRequestGetStatus(Request);
					if (NT_SUCCESS(instStatus)) {
						WDF_REQUEST_COMPLETION_PARAMS completionParams;
						WDF_REQUEST_COMPLETION_PARAMS_INIT(&completionParams);
						WdfRequestGetCompletionParams(Request, &completionParams);

						KdPrint(("Instance get status: 0x%x, Instance id: %ws\n", completionParams.IoStatus.Status, completionParams.IoStatus.Information));
					}
					else {
						KdPrint(("instanceid failed with status 0x%x\n", instStatus));
					}
				}
				else {
					KdPrint(("INstanceID WdfRequestSend failed\n"));
				}

			}
		}

May it be because I haven’t allocated any memory for the output buffer? I saw that every IRPs that returns something say to allocate an output buffer, but the docs for this one didn’t, the only thing said is that the output string can be found in IoStatus.Information

This is a brand-new request, so I don’t understand the call to WdfRequestReuse, but it shouldn’t have any effect on the results.

> @Tim_Roberts said: > This is a brand-new request, so I don’t understand the call to WdfRequestReuse, but it shouldn’t have any effect on the results. I read on the internet that any PnP request must be initialized with STATUS_NOT_SUPPORTED and with WdfRequestReuse is the only way I found to set a status to it

I would have used WdfRequestSetInformarion.

Peter

Ordinarily, KMDF takes care of all of these arcane details automatically. I haven’t found it in the source yet, but I’d be surprised if it wasn’t doing that for you.

And, Peter, WdfRequestSetInformation doesn’t set the Status value…

Sorry, OP and thanks Tim… reading too fast. My bad!

So, let’s try again: I’d use WdfRequestWdmGetIrp!

Gad… I hope I got that right this time :o

Peter