Filtering HID collections and setting I/O flags
Over the past 3 years or so, I have been casually referring to KMDF as the ultimate driver compat layer. Just like Windows has an application compatibility layer which shields applications from OS changes, KMDF provides the same type of compatibility across device stacks. For the most part each stack implements WDM as per the documentation and there is nothing special that KMDF must compensate for when inserted into the stack. HID is not one of these stacks ;). First, let me describe the HID stack.
+---------+ | Raw PDO | +---------+ | | +----------------+ +----| HID miniport + | | HIDclass | +----------------+ | +----------------+ | PDO | +----------------+
HIDClass (the port driver) will enumerate a raw PDO for each top level collection (TLC) in a device's HID descriptor (in this picture there is only one raw PDO, but there could be many). A raw PDO is special in that it does not need an additional INF to be started, all of the functionality that is usually found in a stack's FDO is in the raw PDO. This includes the DeviceObject Flags which specify the buffering type for reads and writes on the device (more on this later). A raw PDO can have a FDO and filters layered on top of it, this is how the HID keyboard and mouse stacks work. A TLC which is neither a keyboard nor a mouse can have a filter installed on it as well.
Setting the flags is where HID does not play by the rules. Typically a bus driver will specify the flags (DO_DIRECT_IO or DO_BUFFERED_IO) which dictate the buffering in read and write IRPs when the device object is created. This would allow an FDO or filter to determine the I/O type during the FDO or filter's AddDevice routine and copy them into its own device object (since the I/O manager looks at the top of the stack to determine buffering for reads and writes). HIDClass on the other hand sets one of the flag (DO_DIRECT_IO) during IRP_MN_START_DEVICE! This means that in a WDM driver filtering on top of a raw HID PDO, you must special case this behavior and set these flags in your device object during IRP_MN_START_DEVICE processing. KMDF takes care of this for you, so your HID filter code is no different than a filter for any other device stack (this is where the driver compat layer is).
What makes the situation worse is that there is a HID filter sample, firefly, which does not change the I/O type in its device object's Flags, meaning it is now a generic HID filter sample, rather only a HID mouse sample. But how does firefly even work in the mouse stack? Well, the upper edge of the mouse stack (mouclass.sys) always sets DO_BUFFERED_IO and read IRPs are never sent down the stack, so HIDClass would never see a read IRP with the wrong buffer type. This means that the propagation of the buffering flags is not needed.
So how is it that this bug existed HIDClass for so long without being discovered? Remember that HIDClass enumerates its PDOs as raw and 99% of the time, nobody loads on top of them, so there was no other WDM component to interact with this behavior of setting the Flags field at the wrong time.