Mapping MDL's to achieve zero-copy in FSD

Hello, list:

I am writing a file system “proxy” driver for Windows. The driver will be used to proxy file system calls and forward them to a user-space application for processing. The model is similar to the one from FUSE in Linux/OSX land or Dokan (CFBS, etc.) in Windows.

I am currently implementing IRP_MJ_WRITE/IRP_MJ_READ. One “obvious” optimization when transferring large buffers using these IRP’s is to map the client process (i.e. the one doing the ReadFile/WriteFile) I/O buffer directly into the user-mode file system process; this will avoid unnecessary buffer allocation and copying. I am planning to do this using MmMapLockedPagesSpecifyCache() (and MmUnmapLockedPages upon IRP completion) in the user-mode file system process context.

One potential gotcha is that the buffers passed to ReadFile/WriteFile are not necessarily page aligned. [In the case of non-buffered I/O they are sector-aligned but not page aligned.] If I map directly the client process I/O buffer into the user-mode file system process, the user-mode file system will be able to read and/or modify content outside the bounds of the I/O buffer. For example, if a buffer starts at 0x10000200 it would be sector aligned, but the user-mode file system would be able to read/write memory contents from 0x10000000 to 0x10000200.

A solution to this problem is to treat the start and end of an I/O buffer specially when the buffer is not page aligned. Suppose that I receive an MDL with such a buffer. I would like to convert it to an MDL that is “safe”, in that it does not allow the user-mode file system to poke around where it does not belong. My technique for achieving this is as follows:

  1. Ensure that the source MDL has been probed/locked.

  2. IoAllocateMdl a target MDL with the same page count as the source MDL and RtlCopyMemory the PFN array from the source MDL to the target MDL.

  3. Allocate from 0 to 2 pages of non-paged pool using ExAllocatePoolWithTag(NonPagedPool, …). Initialize them to zero, then copy the correct portion(s) of the I/O buffer onto the pool pages.

  4. IoAllocateMdl a temporary MDL array to describe the non-paged pool and then call MmBuildMdlForNonPagedPool on it.

  5. If the start of the I/O buffer is not page aligned set the target MDL PFN array 0-th element to the value from the temporary MDL. Likewise for the end of the I/O buffer (and setting the last element of the target PFN array).

  6. Free the temporary MDL array.

  7. MmMapLockedPagesSpecifyCache the target MDL array in the user-mode file system process.

The target MDL is “safe” because regardless of whether the user-mode file system process reads/writes outside of the bounds of the passed I/O buffer it cannot harm the client process.

Which brings me to my question. Will this technique work and create an MDL that will work for my purposes? I am particularly weary of mixing the PFN arrays from the source and temporary MDL’s.

Bill Zissimopoulos

PS: An alternative to this scheme may be to set the DeviceObject’s SectorSize to always be a multiple of PAGE_SIZE. But I would rather not force the SectorSize like that unless I really have to.

>the client process I/O buffer into the user-mode file system process, the user-mode file system will

be able to read and/or modify content outside the bounds of the I/O buffer.

User-mode file system is also a part of your framework.

Guarding yourself from bugs in your own code is probably an overkill.


Maxim S. Shatskih
Microsoft MVP on File System And Storage
xxxxx@storagecraft.com
http://www.storagecraft.com

Maxim, thanks for you answer.

Although I am developing this driver for my own file system (secfs), I do intend to open up the driver for others to use at a later time.

In any case I am (slowly) progressing through IRP_MJ_WRITE at the moment and I will soon try the technique above and report back.

Bill