Returning failure from DriverEntry

One thing that is easily overlooked about implementing DriverEntry is that upon return !NT_SUCCESS, DriverUnload is not called.  I mentioned this anecdotally in a previous post, but it is worth expanding on.  I was bit by this oversight when I was working on the Bluetooth stack.  Driver verifier  correctly identified that my driver had leaked pool.  The code looked something like this

 // Globals
UNICODE_STRING gRegistryPath = { 0 };

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    NTSTATUS status;

    DriverObject->DriverUnload = DriverUnload;

    gRegistryPath.Length = RegistryPath->Length;
    gRegistryPath.MaximumLength = RegistryPath->MaximumLength;
    gRegistryPath.Buffer = (PWCHAR) ExAllocatePoolWithTag(PagedPool, gRegistryPath.MaximumLength, [tag]);
    if (gRegistryPath.Buffer == NULL) { return STATUS_INSUFFICIENT_RESOURCES; }
    RtlCopyMemory(gRegistryPath.Buffer, RegistryPath->Buffer, gRegistryPath.Length);

    status = RegisterWithPortDriver(DriverObject, ...);
    if (!NT_SUCCESS(status)) { return status; } <== leak right here!

    // ... other init ...

    return status;
}

void DriverUnload(PDRIVER_OBJECT DriverObject)
{
    ExFreePool(gRegistryPath.Buffer);
    RtlZeroMemory(&gRegistryPath, sizeof(gRegistryPath));
}

While many WDM drivers do very little outside of initializing the dispatch table and other fields in their DriverObject, a miniport driver or a KMDF driver must register with their port driver (like ScsiPortInitialize) or framework (WdfDriverCreate) and this registration can introduce failure in DriverEntry (just like in my code sample above).  What to do? 

In a WDM driver you have to be very careful and manage this manually.  Either you have a common error exit path out of DriverEntry which performs the cleanup (or manually calls your DriverUnload routine) or cleanup on each possible point of error.  This pattern is very easy to get wrong and is not very maintainable, it is quite easy to add a new allocation and forget to cleanup it up later.

In a KMDF driver things are a bit easier to manage if you follow a particular pattern.  While EvtDriverUnload has the same problems as the WDM DriverUnload, the EvtObjectCleanup routine registered on the WDFDRIVER is called in both scenarios.  To re-emphasize, the EvtObjectCleanup registered on WDFDRIVER will be called when either DriverEntry returns !NT_SUCCESS or if your driver is gracefully unloaded later.  This means that if you put all of your cleanup in the cleanup routine your DriverEntry implemention becomes much simpler.  The one caveat is that the call to WdfDriverCreate must come before any allocations in your driver or state chaning APIs.  WPP_INIT_TRACING is one such state changing API where you must undo its effects by calling WPP_CLEANUP.  Quite a few WDK samples show this pattern (although suprisingly to me, not all!), let us look at the nonpnp sample (%wdk%\src\kmdf\nonpnp\sys\nonpnp.c)

 NTSTATUS
DriverEntry(
    IN OUT PDRIVER_OBJECT   DriverObject,
    IN PUNICODE_STRING      RegistryPath
    )
{
    NTSTATUS                       status;
    WDF_DRIVER_CONFIG              config;
    WDFDRIVER                      hDriver;
    PWDFDEVICE_INIT                pInit = NULL;
    WDF_OBJECT_ATTRIBUTES          attributes;

    WDF_DRIVER_CONFIG_INIT(&config, WDF_NO_EVENT_CALLBACK);

    // Tell the framework that this is non-pnp driver so that it doesn't set the default AddDevice routine.
    config.DriverInitFlags |= WdfDriverInitNonPnpDriver;

    // NonPnp driver must explicitly register an unload routine for the driver to be unloaded.
    config.EvtDriverUnload = NonPnpEvtDriverUnload;

     // Register a cleanup callback so that we can call WPP_CLEANUP when<br>    // the framework driver object is deleted during driver unload. 
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.EvtCleanupCallback = NonPnpEvtDriverContextCleanup;

    status = WdfDriverCreate(DriverObject,
                            RegistryPath,
                            &attributes,
                            &config,
                            &hDriver);
    if (!NT_SUCCESS(status)) {
        KdPrint (("NonPnp: WdfDriverCreate failed with status 0x%x\n", status));
        return status;
    }

     // Since we are calling WPP_CLEANUP in the DriverContextCleanup<br>    // callback we should initialize WPP Tracing after WDFDRIVER<br>    // object is created to ensure that we cleanup WPP properly<br>    // if we return failure status from DriverEntry. This<br>    // eliminates the need to call WPP_CLEANUP in every path<br>    // of DriverEntry. 
    WPP_INIT_TRACING( DriverObject, RegistryPath );

    ...

    return status;
}

VOID NonPnpEvtDriverContextCleanup(WDFDRIVER Driver)
{
    WPP_CLEANUP(WdfDriverWdmGetDriverObject(Driver));
}

The red comments show what is going on.  Hopefully the code is self explanatory. 

Incidentally, another pattern you can use for global memory allocations is to allocate the memory with WdfMemoryCreate without specifying a parent object.  The WDFDRIVER will be the parent object by default and since all child objects are destroyed when the parent is destroyed, all of your allocations will be destroyed after EvtDriverUnload has been called when the WDFDRIVER is destroyed in the unload path.