Proper way to create, format, and send your own WDFREQUEST down the stack in a filter driver.

I am making a lower filter driver for a custom mouse device. I was able to forward the EvtIoRead requests I got from mouhid to my local IO target with both send and forget and with a completion routine that allowed me to modify the results before completing the request for mouhid. But from experimenting like this it seems mouhid sends only a single EvtIoRead request down the stack, and, when forwarded to (hidusb.sys) below my filter driver, it just sits on the request until the user moves the mouse, at which point it calls my completion routine. I need to constantly send mouse events to mouhid (scroll events) based on custom data from the mouse, to achieve very smooth scrolling. I am inexperienced with writing drivers, and so as it stands now I don’t know of any other way to send a mouse event back to mouhid except by completing its EvtIoRead requests. What I thought I would do is just save mouhid’s WDFREQUEST in my device context and instead send a WDFREQUEST I created myself down the driver stack to be completed when an actual mouse event occurs from the device. Then I could write to mouhid’s request output buffer and complete the request everytime I need to send a very small delta scroll event (8ms or so), and also copy the output buffer of my own requests sent down the stack to mouhid’s request and complete it when an actual mouse event occurs. The problem with this is when attempting to send a WDFREQUEST I create myself down the stack, my callback routine is immediatley called and the request status is STATUS_PRIVILEGE_NOT_HELD. I was puzzled by this, but stumbled upon a post by Dolon Holan that stated that when sending a request you need a file object. I thought that would be automatically taken care of by called WdfIoTargetFormatRequestForRead, but after checking the local IO target with WdfIoTargetWdmGetTargetFileObject, I found that it returned NULL. So, since Dolon Holan mentioned opening your own IO Target, I did that, and opened a second (remote?) IO target to the file object in mouhid’s request using WdfRequestGetFileObject => WdfFileObjectWdmGetFileObject => WDF_IO_TARGET_OPEN_PARAMS_INIT_EXISTING_DEVICE(&openParams, wdmFile->DeviceObject) and then openParams.TargetFileObject = wdmFile. I then sent my driver created request to this new IO target, and did not get STATUS_PRIVILEGE_NOT_HELD. Instead I got a null pointer access from hidusb.sys and !analyze -v saying IRQL_NOT_LESS_OR_EQUAL (a)… Arg1: 0000000000000000, memory referenced. So I would like to ask, what is the correct way to send your own driver-created WDFREQUEST to your IO target? Any idea why attempting to send my request to the local IO target resulted in STATUS_PRIVILEGE_NOT_HELD? Is there anything that looks wrong in my below code as to why I would get a memory access error? And/or is there any other way to send my own mouse input to mouhid except completing its EvIoRead requests? I am 99% the mouse input report buffer I am creating is the right size. Also in the !analyze -v output it looks like it’s trying to access a null pointer.

// I tried this first -- immediate completion callback with request status STATUS_PRIVILEGE_NOT_HELD
VOID MouseDriverLowerKMDFEvtIoRead(_In_ WDFQUEUE Queue, _In_ WDFREQUEST Request, _In_ size_t Length)
{
    WDFDEVICE                   device;
    PDEVICE_EXTENSION           deviceContext;
    WDF_REQUEST_PARAMETERS      params;
    NTSTATUS                    status;
    WDFIOTARGET                 ioTarget;
    WDF_REQUEST_SEND_OPTIONS    sendOptions;
    BOOLEAN                     sendResult;
    size_t                      readLength;

    device = WdfIoQueueGetDevice(Queue);
    deviceContext = GetDeviceContext(device);
    deviceContext->mouhidReadRequest = Request;
    ioTarget = WdfDeviceGetIoTarget(device);
    
    WDF_OBJECT_ATTRIBUTES attributes;
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = device;
    WDFREQUEST req;
    
    // I tried this...
    //status = WdfRequestCreateFromIrp(&attributes, WdfRequestWdmGetIrp(Request), FALSE, &req);
    
    // And this. Both resulted in an immediate completion callback with request status
    // STATUS_PRIVILEGE_NOT_HELD
    status = WdfRequestCreate(&attributes, ioTarget, &req);
    
    WDFMEMORY mem;
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = req;
    status = WdfMemoryCreate(&attributes, NonPagedPool, 0, sizeof(MOUSE_INPUT_REPORT), &mem, NULL);
    status = WdfIoTargetFormatRequestForRead(ioTarget, req, mem, NULL, NULL);
    WdfRequestSetCompletionRoutine(req, MouseDriverLowerKMDFEvtCompletionRoutine, NULL);
    WDF_REQUEST_SEND_OPTIONS sendOp;
    WDF_REQUEST_SEND_OPTIONS_INIT(&sendOp, 0);
    sendResult = WdfRequestSend(req, ioTarget, &sendOp);
}
// I tried this next -- no STATUS_PRIVILEGE_NOT_HELD this time but a null pointer access by hidusb.sys
VOID MouseDriverLowerKMDFEvtIoRead(_In_ WDFQUEUE Queue, _In_ WDFREQUEST Request, _In_ size_t Length)
{
    WDFDEVICE                   device;
    PDEVICE_EXTENSION           deviceContext;
    WDF_REQUEST_PARAMETERS      params;
    NTSTATUS                    status;
    WDFIOTARGET                 ioTarget;
    WDF_REQUEST_SEND_OPTIONS    sendOptions;
    BOOLEAN                     sendResult;
    size_t                      readLength;

    device = WdfIoQueueGetDevice(Queue);
    deviceContext = GetDeviceContext(device);
    deviceContext->mouhidReadRequest = Request;

    WDFFILEOBJECT file = WdfRequestGetFileObject(Request);
    if (file != NULL && deviceContext->target == NULL)
    {
        WDF_OBJECT_ATTRIBUTES attributes;
        WDF_IO_TARGET_OPEN_PARAMS  openParams;
        PFILE_OBJECT targetFile = WdfFileObjectWdmGetFileObject(file);
        if (targetFile != NULL)
        {
            WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
            attributes.ParentObject = device;
            status = WdfIoTargetCreate(device, &attributes, &ioTarget);
            if (NT_SUCCESS(status))
            {
                WDF_IO_TARGET_OPEN_PARAMS_INIT_EXISTING_DEVICE(&openParams, targetFile->DeviceObject);
                openParams.TargetFileObject = targetFile;
                status = WdfIoTargetOpen(ioTarget, &openParams);
                if (!NT_SUCCESS(status)) { WdfObjectDelete(ioTarget); }
                else { deviceContext->target = ioTarget; }
            }
        }
    }

    if (deviceContext->target != NULL) ioTarget = deviceContext->target;
    
    WDF_OBJECT_ATTRIBUTES attributes;
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = device;
    WDFREQUEST req;
    
    // This results in an immediate null pointer access after WdfRequestSend
    status = WdfRequestCreate(&attributes, ioTarget, &req);
    
    
    // This actually does not! But when I delete this temporary request in the completion callback,
    // it seems like it deletes mouhid's request output buffer as well because
    // WdfRequestRetrieveOutputBuffer returns STATUS_BUFFER_TOO_SMALL, and length of 0.
    
    //status = WdfRequestCreateFromIrp(&attributes, WdfRequestWdmGetIrp(Request), FALSE, &req);

    
    WDFMEMORY mem;
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = req;
    status = WdfMemoryCreate(&attributes, NonPagedPool, 0, sizeof(MOUSE_INPUT_REPORT), &mem, NULL);
    status = WdfIoTargetFormatRequestForRead(ioTarget, req, mem, NULL, NULL);
    WdfRequestSetCompletionRoutine(req, MouseDriverLowerKMDFEvtCompletionRoutine, NULL);
    WDF_REQUEST_SEND_OPTIONS sendOp;
    WDF_REQUEST_SEND_OPTIONS_INIT(&sendOp, 0);
    sendResult = WdfRequestSend(req, ioTarget, &sendOp);
}
    
// Last thing I tried was creating my own IRP using this code
PIRP            irp;
irp = IoBuildAsynchronousFsdRequest(
        IRP_MJ_READ,
        device,
        &deviceContext->inputReport,
        sizeof(MOUSE_INPUT_REPORT),
        NULL, // Optional
        NULL
    );
    
// Then I created a request from it with code identical to above.
// Doing this still resulted in an immediate memory access error after calling
// WdfRequestSend.
// The completion routine looks like this
    status = WdfRequestGetStatus(Request);

    device = WdfIoTargetGetDevice(Target);
    deviceContext = GetDeviceContext(device);
    ioTarget = Target;

    report = WdfMemoryGetBuffer(Params->Parameters.Read.Buffer, &readLength);

    //WdfObjectDelete(Request); // Seems to delete mouhid's request's buffer as well, possibly because I created my request from its IRP

   // STATUS_BUFFER_TOO_SMALL and readLength = 0
    status = WdfRequestRetrieveOutputBuffer(deviceContext->mouhidReadRequest, sizeof(MOUSE_INPUT_REPORT), &outReport, &readLength);
    // ...
    WdfRequestCompleteWithInformation(deviceContext->mouhidReadRequest, STATUS_SUCCESS, sizeof(MOUSE_INPUT_REPORT));

The output of !anaylze -v from the bad memory access is below

IRQL_NOT_LESS_OR_EQUAL (a)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high.  This is usually
caused by drivers using improper addresses.
If a kernel debugger is available get the stack backtrace.
Arguments:
Arg1: 0000000000000000, memory referenced
Arg2: 0000000000000002, IRQL
Arg3: 0000000000000000, bitfield :
	bit 0 : value 0 = read operation, 1 = write operation
	bit 3 : value 0 = not an execute operation, 1 = execute operation (only on chips which support this level of status)
Arg4: fffff8027a426312, address which referenced memory
...
BUGCHECK_CODE:  a
BUGCHECK_P1: 0
BUGCHECK_P2: 2
BUGCHECK_P3: 0
BUGCHECK_P4: fffff8027a426312
READ_ADDRESS:  0000000000000000 
PROCESS_NAME:  csrss.exe
IRP_ADDRESS: ffffae0c9b5b2a20
DEVICE_OBJECT: ffffae0c9b8e6060
DRIVER_OBJECT: ffffae0c9af04e30
IMAGE_NAME:  hidusb.sys
MODULE_NAME: hidusb
FAULTING_MODULE: fffff8029e650000 hidusb
STACK_COMMAND:  .cxr; .ecxr ; kb
FAILURE_BUCKET_ID:  AV_IMAGE_hidusb.sys
OS_VERSION:  10.0.19041.1
BUILDLAB_STR:  vb_release
OSPLATFORM_TYPE:  x64
OSNAME:  Windows 10
FAILURE_ID_HASH:  {6323539b-739e-4b0b-6e07-ed3d6d6093f3}

Any idea what am I doing wrong and what is the right way to send a personally created WDFREQUEST down the stack? Appreciate any guidance and thanks.

To read data, HIDclass requires that you have an open file object where you requested read access. your filter can potentially do this one of two ways: 1) open its own file object or 2) reuse mouhid’s file object. option 1) is not possible because mouhid opens the mouse with exclusive read access, so you are stuck with option 2). this means your first attempt is the way to go and you are almost there. you need to copy the file object from the sender’s request into your newly requested request.

What you must also consider

  1. how to handle cancelation. You need to put the sender’s request into a manual queue so that you can complete it later and allow it to be cancelable.
  2. when the sender’s request is canceled, you need to make sure that all of the requests you send down the stack on your own have been canceled and completed back to you (since you are “borrowing” the file object)

the simplest way to solve 2 is to create your own WDFIOTARGET and open it with the next lower attached device object, WDF_IO_TARGET_OPEN_PARAMS_INIT_EXISTING_DEVICE(&openParams, WdfDeviceWdmGetAttachedDevice function (device)) and send your requests on this iotarget using the code below (replace WdfDeviceGetIoTarget) with the new one). Then stop the io target when the sender’s request is canceled and you will have to handle the stop asynchronously (WdfIoTargetCancelSentIo, not WdfIoTargetWaitForSentIoToComplete) as cancellation can happen at dispatch_level and you can’t synchronously wait at this IRQL

VOID MouseDriverLowerKMDFEvtIoRead(_In_ WDFQUEUE Queue, _In_ WDFREQUEST Request, _In_ size_t Length)
{
    WDFDEVICE                   device;
    PDEVICE_EXTENSION           deviceContext;
    WDF_REQUEST_PARAMETERS      params;
    NTSTATUS                    status;
    BOOLEAN                     sendResult;
    size_t                      readLength;
    PIRP newIrp, fdoIrp;
    
    device = WdfIoQueueGetDevice(Queue);
    deviceContext = GetDeviceContext(device);
    deviceContext->mouhidReadRequest = Request;
    ioTarget = WdfDeviceGetIoTarget(device);

    WDF_OBJECT_ATTRIBUTES attributes;
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = device;
    WDFREQUEST req;

    status = WdfRequestCreate(&attributes, ioTarget, &req);

    WDFMEMORY mem;
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = req;
    status = WdfMemoryCreate(&attributes, NonPagedPool, 0, sizeof(MOUSE_INPUT_REPORT), &mem, NULL);
    status = WdfIoTargetFormatRequestForRead(ioTarget, req, mem, NULL, NULL);
    
    newIrp = WdfRequestWdmGetIrp(request);
    fdoIrp = WdfRequestWdmGetIrp(Request);

    // IMPORTANT: this must come after the WdfIoTargetFormatRequestForRead call so that we update the next stack location (the WDF API will zero it out before formatting it)
    // copy the sender's file object into the next stack location so that the next lower device in stack can retrieve it
    IoGetNextIrpStackLocation(newIrp)->FileObject = IoGetCurrentIrpStackLocation(fdoIrp)->FileObject;

    WdfRequestSetCompletionRoutine(req, MouseDriverLowerKMDFEvtCompletionRoutine, NULL);

    sendResult = WdfRequestSend(req, ioTarget, NULL);
}