Thursday, January 23, 2014

Getting Started with Windows File System Filters - DriverEntry

Now that we have everything in place (meaning the INF file with the Microsoft allocated altitude), we're ready to start coding. Minifilters are regular WDM drivers (well, they implement a subset of the features; no power manager or plug and play) and so their main() function is DriverEntry(). A minifilter's DriverEntry() is very simple and typically only registers the minifilter with FltMgr and starts filtering. Of course, a minifilter can choose a different flow, where it starts filtering (or even registers with FltMgr) at a later point. In my experience these are pretty rare so I'll just discuss the usual case.
So, as explained before, there are two steps to "starting" a minifilter:
  1. Registering it with FltMgr - This is done by calling FltRegisterFilter() and it is during this time where the FLT_FILTER structure is allocated by FltMgr. When the function returns the system can see the filter but filtering hasn't started yet, no callbacks are being called.
  2. Starting filtering - This is done in the call to FltStartFiltering() and a minifilter is expected to be ready to process callbacks even before the function returns. This separation is useful for cases where the filter needs to set up some additional things before it's ready to do any work, things that might require some time (for example it might need to create some threads).
The only potentially difficult thing here is dealing with the many fields of the FLT_REGISTRATION structure. It has its own documentation page, but i'd like to quickly go over the fields:
  • Size - this is pretty obvious, just sizeof(FLT_REGISTRATION).
  • Version - this has changed a couple of times. Mainly exists for future compatibility. Look in fltkernel.h if you'd like to see what the versions look like.
  • Flags - The values here are documented and pretty easy to understand. Keep in mind that support for NPFS and MSFS is only available for Win8 and newer.
  • ContextRegistration - Now we're getting into the tricky bits. This is an array of structures that define which types of contexts the filter will use. This concept of the array of structures might feel a bit strange initially, but it works out great for things that are really sparse. For example there are 7 types of context and most applications only use one or two.
  • OperationRegistration - This is also an array of the IRP_MJ operations you want to filter. I generally start with one or two operations (depending on the style of filter) and just keep adding stuff here.
  • FilterUnloadCallback - Callback for unloading the filter. If NULL the filter can't be unloaded.
  • InstanceSetupCallback - Callback for when an instance gets created.
  • InstanceQueryTeardownCallback - Callback to check whether a manual detach should be allowed.
  • InstanceTeardownStartCallback - Callback to indicate that the instance will start tearing down.
  • InstanceTeardownCompleteCallback - Callback to indicate that the instance has finished tearing down.
  • GenerateFileNameCallback - Name provider callback for generating names.
  • NormalizeNameComponentCallback - Name provider callback for normalizing names.
  • NormalizeContextCleanupCallback - Name provider callback for normalization context cleanup.
  • TransactionNotificationCallback - Callback for transaction management.
  • NormalizeNameComponentExCallback - Another normalization callback for with better transaction support.
  • SectionNotificationCallback - A callback for notifications for sections created by the filter.
As you can see this list is a pretty random set of things that the filter might need. So let's look at the minimal set of things that are required to make a filter:
  • ContextRegistration - for anything other than very trivial filters you'll need some contexts, so this will probably have something.
  • OperationRegistration - same here, you can only do very limited stuff (like watching volumes appear and go away) without any operations to monitor. You'll have at least one callback defined here.
  • FilterUnloadCallback - you can probably skip this initially but you won't be able to unload the filter. Doesn't matter much if you use a VM for debugging but in general I'd advise creating this callback, even if it doesn't do anything.
  • InstanceXxxCallback - you can probably initially skip all these. In my experience it'll be pretty clear when you need to use them.
  • XxxNameXxxCallback - unless you're a name provider you can skip all these. You probably shouldn't make your first filter a name provider anyway.
  • TransactionNotificationCallback - very likely you can skip this initially.
  • SectionNotificationCallback - same here, most likely not necessary.
So basically once you build this structure and call FltRegisterFilter() and FltStartFiltering() from DriverEntry() you should be able to play with your filter. If you don't want to do all the typing take a look at the nullFilter WDK sample. It doesn't really do anything (no operations are defined) but it should load and unload just fine. Easy way if you want a simple file system filter template to build on top of.