Is That Handle Still Valid?

Introduction: The five scenarios

In the kernel-mode driver framework (KMDF), a WDFREQUEST object represents an I/O request. Every WDFREQUEST object is associated with one or more WDFMEMORY objects, and each such WDFMEMORY object represents a buffer that is used for input or output in the request. The following figure shows these relationships.

WDFmem1.gif
Click to view full-size image.

When KMDF creates WDFREQUEST and WDFMEMORY objects to represent an incoming I/O request, it sets the WDFREQUEST object as the parent of the associated WDFMEMORY objects. Thus, the WDFMEMORY object can persist no longer than the lifetime of the WDFREQUEST object. When the KMDF driver completes the I/O request, the framework deletes the WDFREQUEST object and the WDFMEMORY object, so the handles to these two objects become invalid.

The underlying buffer, however, is different. Depending on which component created the buffer and how it created the buffer, the buffer might have a reference count and might be "owned" by the WDFMEMORY object—or it might not. If the WDFMEMORY object owns the buffer, then the buffer has a reference count and its lifetime is limited to that of the WDFMEMORY object. If some other component created the buffer, then the lifetimes of the buffer and the WDFMEMORY object are not related.

A KMDF driver can also create its own WDFREQUEST objects to send to I/O targets. A driver-created request can reuse an existing WDFMEMORY object that the driver received in an I/O request. A driver that frequently sends requests to I/O targets can reuse the WDFREQUEST objects that it creates. It cannot, however, reuse WDFREQUEST objects that it receives from KMDF.

Understanding the lifetimes of the WDFREQUEST object, the WDFMEMORY object, and the underlying buffer is important to ensure that your driver does not attempt to reference an invalid handle or buffer pointer.

Consider the possible usage scenarios:

Scenario 1: Driver receives an I/O request from KMDF, handles it, and completes it.

Scenario 2: Driver receives an I/O request from KMDF and forwards it to an I/O target.

Scenario 3: Driver issues an I/O request that uses a new WDFMEMORY object.

Scenario 4: Driver issues an I/O request that uses an existing WDFMEMORY object.

Scenario 5: Driver reuses a WDFREQUEST object that it created.

Scenario 1: Driver receives an I/O request from KMDF, handles it, and completes it

In the simplest scenario, KMDF dispatches a request to the driver and the driver performs I/O and completes the request. In this case, the underlying buffer might have been created by a user-mode application, by another driver, or by the operating system itself. If the driver performs buffered or direct I/O, it can access the buffer in either of the following ways:

Get a handle to the WDFMEMORY object that is associated with the WDFREQUEST by calling WdfRequestRetrieveInputMemory or WdfRequestRetrieveOutputMemory (depending on whether this is a write or read request), and then get a pointer to the buffer by calling WdfMemoryGetBuffer. To read and write the buffer, the driver calls WdfMemoryCopyFromBuffer or WdfMemoryCopyToBuffer.

Get a pointer to the buffer by calling WdfRequestRetrieveInputBuffer or WdfRequestRetrieveOutputBuffer (depending on whether this is a write or read request).

When the driver calls WdfRequestComplete to complete the I/O request, the framework deletes the WDFMEMORY object. The buffer pointer is then invalid.

Scenario 2: Driver receives an I/O request from KMDF and forwards it to an I/O target

What if the driver forwards the request to an I/O target instead of performing the I/O itself? In this case, the driver would proceed as follows:

1.

Call WdfDeviceGetIoTarget to get a handle to the I/O target object.

2.

Call WdfRequestFormatUsingCurrentType to format the request for the I/O target or call both WdfRequestRetrieveXxxMemory to get the memory object and WdfIoTargetFormatRequestForXxx to format the request for the I/O target.

3.

Call WdfRequestSend to send the request to the I/O target.

The driver must not alter the WDFREQUEST object or the WDFMEMORY object in any way. If the driver sets an I/O completion callback for the request, KMDF invokes the callback after the I/O target has completed the request. The buffer pointer remains valid until the driver (not the I/O target) has called WdfRequestComplete.

The following sample code shows how a driver retrieves a handle to the WDFMEMORY object from an incoming WDFREQUEST object, formats the request to send to the I/O target, and sends the request:

VOID
EvtIoRead(
    IN WDFQUEUE Queue,
    IN WDFREQUEST Request,
    IN size_t Length
    )
{
    NTSTATUS status;
    WDFMEMORY memory;
    WDFIOTARGET ioTarget;
    BOOLEAN ret;
    ioTarget = WdfDeviceGetIoTarget(WdfIoQueueGetDevice(Queue));
 
    status = WdfRequestRetrieveOutputMemory(Request, &memory);
    if (!NT_SUCCESS(status)) {
        goto End;
    }
 
    status = WdfIoTargetFormatRequestForRead(ioTarget,
                                    Request,
                                    memory,
                                    NULL,
                                    NULL);
    if (!NT_SUCCESS(status)) {
        goto End;
    }
 
    WdfRequestSetCompletionRoutine(Request,
                                    RequestCompletionRoutine,
                                    WDF_NO_CONTEXT);
 
    ret = WdfRequestSend (Request, ioTarget, WDF_NO_SEND_OPTIONS);
    if (!ret) {
        status = WdfRequestGetStatus (Request);
        goto End;
    }
 
    return;
 
End:
    WdfRequestComplete(Request, status);
    return;
 
}

When the I/O target has completed the request, KMDF calls the completion callback that the driver set for the request. The following is the code for this routine:

VOID
RequestCompletionRoutine(
    IN WDFREQUEST                  Request,
    IN WDFIOTARGET                 Target,
    PWDF_REQUEST_COMPLETION_PARAMS CompletionParams,
    IN WDFCONTEXT                  Context
    )
{
    UNREFERENCED_PARAMETER(Target);
    UNREFERENCED_PARAMETER(Context);
 
    WdfRequestComplete(Request, CompletionParams->IoStatus.Status);
 
    return;
 
}

When the driver calls WdfCompleteRequest in its completion callback, KMDF destroys the WDFMEMORY object. The WDFMEMORY object handle that the driver retrieved in the EvtIoRead function is now invalid.

Scenario 3: Driver issues a request that uses an existing WDFMEMORY object

Some drivers issue their own I/O requests and send them to I/O targets, which are represented by WDFIOTARGET objects. Such requests must use driver-created WDFREQUEST objects; a driver cannot reuse a WDFREQUEST object that it received from KMDF. However, a driver can retrieve a WDFMEMORY object from an incoming WDFREQUEST and then create a new request that uses that WDFMEMORY object. The driver must not change the underlying buffer, but it can pass a buffer offset when it formats the new I/O request. To format a new I/O request that uses an existing WDFMEMORY object, the driver calls one of the WdfIoTargetFormatRequestXxx or WdfIoTargetSendXxxSynchronously methods. If the driver sends the request asynchronously, it should register an I/O completion callback so that KMDF can notify it when the request is complete.

When KMDF formats the request to send to the I/O target, it takes out a reference on the recycled WDFMEMORY object on behalf of the I/O target object. The I/O target object retains this reference until one of the following occurs:

The request has been completed.

The driver reformats the WDFREQUEST object again by calling one of the WdfIoTargetFormatRequestXxx or WdfIoTargetSendXxxSynchronously methods.

The driver calls WdfRequestReuse.

When the new I/O request is complete, WDF calls the I/O completion callback that the driver set for this request. At this point, the WDFIOTARGET object still holds a reference on the WDFMEMORY object. Therefore, in the I/O completion callback, the driver must call WdfRequestReuse on the driver-created WDFREQUEST object before it completes the original request from which it retrieved the WDFMEMORY object. If the driver does not call WdfRequestReuse, a bug check occurs because of the extra reference.

Scenario 4: Driver issues a request that uses a new WDFMEMORY object

KMDF provides three ways for drivers to create new WDFMEMORY objects, depending on the source of the underlying buffer:

To let KMDF allocate a buffer, a driver calls WdfMemoryCreate. The buffer can be allocated from paged or nonpaged pool, according to the driver's specifications.

To allocate the buffer from a previously defined lookaside list, a driver calls WdfMemoryCreateFromLookaside.

To assign a previously allocated buffer to a new WDFMEMORY object, a driver calls WdfMemoryCreatePreallocated. The driver can later assign a different buffer to the same WDFMEMORY object by calling WdfMemoryAssignBuffer.

If the buffer is allocated by KMDF or from a driver-created WDFLOOKASIDE list, the WDFMEMORY object "owns" the buffer, so the buffer pointer remains valid as long as the WDFMEMORY object exists. Drivers that issue asynchronous I/O requests should always use buffers that are "owned" by WDFMEMORY objects so that KMDF can ensure that the buffers persist until the I/O request has completed back to the issuing driver. Therefore, drivers should use either WdfMemoryCreate or WdfMemoryCreateFromLookaside to create WDFMEMORY objects for asynchronous I/O requests.

If the driver assigns a previously allocated buffer to a new WDFMEMORY object by calling WdfMemoryCreatePreallocated, the WDFMEMORY object does not "own" the buffer. In this case, the lifetime of the WDFMEMORY object and the lifetime of the underlying buffer are not related. The driver must manage the lifetime of the buffer and must not attempt to use an invalid buffer pointer.

A driver cannot use WdfMemoryAssignBuffer with a WDFMEMORY object that it received in an I/O request; it can use this method only with a new, driver-created WDFMEMORY object.

Scenario 5: Driver reuses a WDFREQUEST object that it created

A driver can reuse the WDFREQUEST objects that it creates, but it must reinitialize each such object by calling WdfRequestReuse before each reuse. For sample code that reinitializes a WDFREQUEST object, see the Toastmon and NdisEdge samples that are provided with the KMDF release.

What should you do?

Do not attempt to reference the buffer underlying a WDFMEMORY object after the associated WDFREQUEST object has been completed.

Always use buffers that are allocated by WdfMemoryCreate or WdfMemoryCreateFromLookaside when sending asynchronous I/O requests.

If your driver sends an I/O request that includes a reused WDFMEMORY object, it must always call WdfRequestReuse after the new request is complete, but before completing the request from which it retrieved the WDFMEMORY object.

Always call WdfRequestReuse to reinitialize a WDFREQUEST object before reusing the object in a new I/O request.

For more information:
Architecture of the Kernel-Mode Driver Framework
Kernel-Mode Driver Framework (KMDF)
In the KMDF Documentation, see:

Design Guide

Handling I/O Requests in Framework-based Drivers

Framework Request Objects

Programming Techniques for Framework-based Drivers

Using Memory Buffers



Was This Information Useful?