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:

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.
if (pMdl->MdlFlags & MDL_PAGES_LOCKED) {

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

pMdl = pNext;

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

Comments (3)

  1. dispensa says:

    Wow, makes sense. I’ve never seen that documented anywhere either.

    Seems like IoBuildAsynchronousFsdRequest() would ahve the same issue.

    Could verifier or SDV be enhanced to check for this on IoFreeIrp()? You might also be able to do something creative with IoInitializeIrp() to catch reinits.

  2. Good point.  IoBuildAsynchronousFsdRequest does have the same issue and you need to free the MDL chain for IRPs allocated from it (I will change the entry to reflect that).

    SDV could be enhanced to check for this in IoFreeIrp, but i would think DV might be a better choice.  For SDV to catch this, SDV’s simulation of the device you are sending I/O would have to create the MDL chain as well.  SDV would need to have runtime knowledge of the stack you are sending I/O to to make that decision.  For instance, if SDV did this for all I/O, it would flag I/O sent to a USB enumerated stack as failing this rule, but in reality, USB never allocates the MDL chain so it would be a false positive.

  3. Mjd says:

    Is it ever the case that you'd have free the MDL chain for a KDMF upper filter driver?  

    If one's KDMF filter driver created the WDFREQUEST and sent a read or write down the stack and it's completed to back to the same filter driver?  I ask, as I've got huge MDL leak and I never explicitly allocated any MDL's in my KDML driver, but I can see them created by ntIoAllocateMdl() when, for instance my driver calls WdfIoTargetFormatRequestForWrite(). I never saw the corresponding nt!IoFreeMdl() call.

    Thanks in advance, Doran for any hints or suggestions. -mjd

Skip to main content