File System Minifilter Drivers (Part 1)

For my first technical post I wanted to talk about the Windows component I have been learning about and working with the most, the Filter Manager.  After working on Nooks at college, I came to Microsoft aware of the difficulty in writing a driver for Windows.  When I get really frustrated I compare understanding IRPs to understanding quantum mechanics, but that isn’t exactly a fair comparison.

The Filter Manager was meant to create a simple mechanism for drivers to filter file system operations: file system minifilter drivers.  File system minifilter driver are located between the I/O manager and the base filesystem, not between the filesystem and the storage driver(s) like legacy file system filter drivers.

Now, I should mention that this isn’t a new mechanism; it has been around for a while.  But, undoubtedly some people are aware of it but haven’t used it yet (like me until recently) and I feel it is important that every driver writer be aware of how they work.  If a file system minifilter driver provides the functionality you need then you should write one; file system minifilter drivers are simpler than legacy drivers and hence less prone to bugs (at least theoretically).  If you already know how the Filter Manager works I won’t be providing any inside information or upcoming updates, so feel free to skip this post.

The old mechanism of filtering file system operations (between the filesystem and storage driver(s)) required handling IRPs and the creation/handling of device objects.  On the other hand, file system minifilter drivers that utilize the Filter Manager utilize a callback mechanism.  This callback mechanism specifies what IRPs you are interested in filtering.  The structure for doing this is extremely simple.

const FLT_OPERATION_REGISTRATION FilterCallbacks[] = {

    {IRP_MJ_CREATE,

     0,

     PreCreate,

     PostCreate,

     NULL},

    {IRP_MJ_WRITE,

     0,

     PreWrite,

     NULL,

     NULL},

    {IRP_MJ_CLOSE,

     0,

     PreClose,

     PostClose,

     NULL},

    {IRP_MJ_OPERATION_END}

};

This structure is passed to the Filter Manager (with some other registration information) in a call to FltRegisterFilter.  These callback functions (PreCreate, PostClose, etc.) then need to be defined in the driver and the filter manager ensures that the appropriate functions are called when it receives the IRPs you specified you wanted callbacks for.  The pre-callbacks are for IRPs going from the I/O manager to the base filesystem.  The function signature for pre-callbacks is:

typedef FLT_PREOP_CALLBACK_STATUS
(*PFLT_PRE_OPERATION_CALLBACK) (
IN OUT PFLT_CALLBACK_DATA Data,
IN PCFLT_RELATED_OBJECTS FltObjects,
OUT PVOID *CompletionContext
);

Post-callbacks are for IRPs going in the opposite direction, from the base filesystem to the I/O manager and their function signature is:

typedef FLT_POSTOP_CALLBACK_STATUS
(*PFLT_POST_OPERATION_CALLBACK) (
IN OUT PFLT_CALLBACK_DATA Data,
IN PCFLT_RELATED_OBJECTS FltObjects,
IN PVOID CompletionContext,
IN FLT_POST_OPERATION_FLAGS Flags
);

There are a couple parameter differences between the pre- and post-callbacks, but the general idea is the same.  The Data parameter specifies information about an I/O operation.  Basically it represents the IRP information but in a simple to use container.  The FltObjects parameter provides pointers to objects related to the operation including the volume and file object.  The CompletionContext parameter is extremely helpful for passing information between the callbacks.  Any pre-callback can set CompletionContext and that pointer will be passed in to the post-callback.

 

The CompletionContext mechanism and context support are my two most used components of the Filter Manager.  While CompletionContext allows you to attach information to a specific IRP operation, the Filter Manager’s context support allows you to attach information to a volume, instance, file, stream, or stream handle (support for all of these are not yet completed, so check the latest information in MSDN to see what is currently supported).  While being able to transfer information to a post-callback is useful, sometimes higher-level tracking is required.  For instance, a filter driver wanting to track all operations across the lifetime of a particular file open would use stream handles (which operates at the file object level).

 

In my next post I will continue with a more in-depth discussion of the Filter Manager’s context support.  Anyone wanting to learn more about file system minifilter drivers should look at MSDN and the file system minifilter driver WHDC page.