Questions on MDL handling in Microsoft's SwapBuffers sample.

Hi folks, I’ve read the following articles:

and have the following questions on the sample (https://github.com/microsoft/Windows-driver-samples/blob/main/filesys/miniFilter/swapBuffers/swapBuffers.c):

Q1

In read pre-op (SwapPreReadBuffers) we allocated our own MDL (newMdl) and swapped it with the IRP’s MDL:

L953: iopb->Parameters.Read.MdlAddress = newMdl;

We did not save the original MdlAddress before overwriting it. If it’s not null, then who frees it? The comments at the end of post-op (SwapPostReadBuffers, L1200) states that “freeing of the MDL (if there is one) is handled by FltMgr”. There are 2 MDLs here, and I guess FltMgr handles one of them, so what about the other one?

Q2

The aforementioned comments state that the filter manager is the one that does the freeing. However, the IFS FAQ states that it’s the I/O Manager that cleans up the MDL:

A normal driver will associate an MDL it creates to describe the user buffer with the IRP. This
is useful because it ensures that when the IRP is completed the I/O Manager will clean up these
MDLs, which eliminates the need for the driver to provide special handling for such clean-up.

Is this a typo with the code comments, or is this really the case for minifilters?

Q3

In read post-op (SwapPostReadBuffers), we did not call MmProbeAndLockPages on MdlAddress, but proceeded to call MmGetSystemAddressForMdlSafe on it (L1089). How is this safe? The “Master of the Obvious” article states:

When a driver specifies METHOD_DIRECT, the I/O manager guarantees that the driver will receive
an MDL that describes the full virtual address range specified by the original requestor and
that the MDL has been probed and locked.

Is this the reason why?

Q4

In read post-op, handling of the case of user buffer is deferred via FltDoCompletionProcessingWhenSafe. Instead of doing this, can we do the following?

  1. In pre-op, use IoAllocateMdl to create a MDL for the user buffer
  2. Call MmProbeAndLockPages on it
  3. Pass MDL to post-op via completion context
  4. In post-op call MmGetSystemAddressForMdlSafe on it
    We would be doing all the work directly in pre and post callbacks, so FltDoCompletionProcessingWhenSafe is not needed.

Q5

If the aforementioned approach is possible, then can we take it a step further by doing all the work of getting the system address of the original buffer (be it a user buffer, system buffer, or MdlAddress) in read pre-op, and then pass the address to post-op via the completion context? This keeps all the related code in the same scope and thus facilitates comprehension and maintenance. Are there any ptifalls/gotchas with this approach?

This sample has caused nothing but confusion ? I always get questions about it when I teach our minifilter seminar, would probably be a good article to go through and explain what the hell it’s doing , why it’s doing it (hint: dummy pages), and the behaviors it relies upon…

Q1/2
Those articles you reference are all from the perspective of the core O/S. Remember that minifilters talk to FltMgr and FltMgr can make up whatever rules it wants, which is what leading to the confusion here.

FltMgr “knows” that the MDL address has changed and keeps track of both so that they can be properly freed. This is not clear from any documentation that I’m aware of even though this sample relies heavily upon that behavior.

Q3

There’s no need to probe and lock because this MDL is for a non-paged pool buffer and was built using MmBuildMdlForNonPagedPool

Q4

Yes, that would work assuming that you don’t need to do anything else in the PostOp that might require < DISPATCH_LEVEL. Also, if you’re going to be doing work that takes “a long time” you don’t want to do that at DISPATCH_LEVEL (“long time” is subjective and not easily definable, just something to think about).

Q5

Nothing wrong with that…As a general rule I like to do anything that might fail in PreXxx and pass along the results to post. It’s nice to keep the majority of failure cases in Pre and not have to worry about failing AFTER the file system has done its work and the affects that might have (e.g. timestamps).

Thanks Scott!
For Q4, pre-op did allocate a non-paged pool buffer and built the MDL using MmBuildMdlForNonPagedPool. It then swapped the MdlAddress field with the new MDL. However in post-op, the FLT_PARAMETERS field would be back to the original values, i.e. MdlAddress now points to the original MDL, not our swapped one. So to confirm, you’re saying we can be sure that the original MDL is also for a non-paged pool buffer and was built using MmBuildMdlForNonPagedPool?

Ah, sorry, you’re asking about the original buffer…

If there was an MDL with the request then it must already be locked by either using MmProbeAndLockPages or MmBuildMdlForNonPagedPool. You could tell by the flags if you wanted but it doesn’t really matter, the key point is that the physical pages are locked (which is a requirement prior to mapping)