An easy way to leak pool and locked pages (e.g. sometimes you have to free Irp->MdlAddress)

Did you know that there are times when you must free Irp->MdlAddress in your driver? Even more importantly, not only freeing Irp->MdlAddress , but the entire chain of MDLs? But even more importantly then freeing the entire chain, but also unlocking the MDL's pages? I didn't know this little tidbit until it was pointed out to be earlier this year.

So when must you free the MDL chain in the PIRP?

Here are some guidelines as to when:

  1. This only applies to PIRPs that you allocate on your own (e.g. calling IoAllocateIrp() or IoIntializeIrp() on a chunk of memory). If the PIRP was presented to your driver in a dispatch routine, you don't have to do a thing.
  2. As an exception to the first rule, if you allocate the PIRP using any of the IoBuildXxx() routines, you don't have to do a thing because the I/O manager will free the chain for you.   Of course, nothing in life is simple, there is an exception to this as well.  If you allocate a PIRP by calling IoBuildAsynchronousFsdRequest(), you will have to free the MDL chain.
  3. 99.99% this only applies if you are sending I/O to a device outside of your stack (there is the slight change that an in stack PIRP interface requires this behavior, but I haven't seen one). For instance, you don't have to do this for PIRPs that you send down a USB or HID enumerated stack.

Before I go into why you would free the MDL chain, I want to explain why you don't have to free it for a PIRP presented to your dispatch routine or for PIRPs created by a call to IoBuildXxx(). The reason is that for these types of PIRPs, or as they are sometimes called, threaded PIRPs (because they are enqueued in a thread's list of pending PIRPs), the I/O manager is the one freeing the MDL chain the PIRP since the I/O manager owns the PIRP after it has been fully completed.

So, finally (!!!) , why must you free the MDL chain? Well, there are drivers which create the MDL chain as a part of processing the I/O and then rely on the I/O manager to free the chain on its behalf when the PIRP completes back to the I/O manager. Since you are sending a PIRP to this driver that is not owned by the I/O manager, you must fulfill the role of the I/O manager and free the MDL chain.

But which drivers do this? As far as I know, only the file system drivers do this, so if you are hand crafting your own PIRPs to send to a file system (which begs the question why you are not calling ZwReadFile(), ZwWriteFile(), or ZwDeviceIoControl() in the first place), you should especially be aware of this issue. To be safe, you should do this for hand created I/O that you send to any device object that you retrieved by calling IoGetDeviceObjectPointer() or ZwCreateFile().

Freeing and unlocking the MDL chain is rather simple, here is an example of how to do it:

 VOID
FreeIrpMdlChain(
    PIRP Irp
    )
{
    PMDL pMdl, pNext;

    pMdl = Irp->MdlAddress;

    //
    // Free any PMDLs that the lower layer allocated.  Since we are going
    // to free the PIRP ourself and not call IoCompleteRequest, we must mimic
    // the behavior in IoCompleteRequest which does the same thing.
    //
    while (pMdl != NULL) {
        //
        // The contract is that all the MDLs in the chain should have their pages locked,
        // the I/O manager makes this assumption as well.
        //
        ASSERT(pMdl->MdlFlags & MDL_PAGES_LOCKED);
        if (pMdl->MdlFlags & MDL_PAGES_LOCKED) {
            MmUnlockPages(pMdl);
        }

        //
        // Capture the next MDL before we free the current MDL
        //
        pNext = pMdl->Next;

        IoFreeMdl(pMdl);
        pMdl = pNext;
    }

    //
    // Clear MdlAddress to make sure no other component touches a freed MDL by mistake.
    //
    Irp->MdlAddress = NULL;
}