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.

Thursday, January 16, 2014

Getting Started with Windows File System Filters - What's That INF File For ?

In a nutshell, INF files are files that contain parameters for drivers. There is an MSDN page for INF files in general and then there is a file system filter specific MSDN page. I won't go over the information there, which is very comprehensive, I'd rather focus a bit on how I work with them and what sort of decisions a file system designer might need to make.

In terms of how often does one use the INF files, I use the INF file every time I need to install a new version of my minifilter. My workflow is to rebuild the SYS file and copy it to a location shared with the VM, and then I need the INF to load it. Sometimes I load it by right-clicking the INF file and then clicking on Install:
This is fine when I just want to play with a filter but when actively working on it (when I'm building it 40 times a day) I use a script to load it. It's usually a one-line script but it can grow if I need to initialize other things as well:
rundll32.exe setupapi.dll,InstallHinfSection DefaultInstall 128 nullfilter.inf
The magic value "128" is documented on the page for the InstallHinfSection Function. I never need to reboot for development versions of my drivers so 128 works for me. I remove any bits that might require a reboot and if I need to run other services and such I just add more lines to my installation script for that.
Now, for most filters I've ever needed to work on I just take one of the INF files for any of the file system filter samples (nullfilter is the one I use as a template most often, for more advanced usage see simrep and minispy) and I change a couple of things:
  • The driver name. This is pretty much a straight find&replace, but make sure you preserve the case.
  • The LoadOrderGroup. This is the big one. Each file system filter developer needs to pick the right LoadOrderGroup for their filter, depending on what they plan do to. All the various load orders are listed on the MSDN page Load Order Groups for File System Filter Drivers. For an in-depth look at how loading a filter works and how to more closely control the load order see my previous post on Controlling the Load Order of File System Filters. However, at the time of writing the INF all one needs to worry about is the proper LoadOrderGroup.
  • The Class and ClassGuid. This is closely related to the LoadOrderGroup, basically just copy the name and the GUID from the MSDN page File System Filter Driver Classes and Class GUIDs.
  • The altitude. You need to get this from Microsoft, they allocate unique ones for each filter. If you follow the steps on the page Minifilter Altitude Request you should receive your altitude in an email. This might take a couple of weeks or more (AFAIK it's a manual process and each request is reviewed to make sure that the LoadOrderGroup for the filter type makes sense (based on the brief description in the submission form; thus it helps to explain what the filter does fairly clearly, to expedite the process)). But again, I don't think there are any guarantees when it comes to the time, so you want to do this early in the development process and not wait till the last minute. However, when starting development you'll need an altitude to load your filter so what I normally do is just pick an unused one in the right LoadOrderGroup from the page Allocated Altitudes while I wait for my request to be processed. Please keep in mind that this page isn't updated all the often so it's possible that the altitude you picked has already been allocated to someone else. But in general when you get the new altitude it's simply a matter of replacing it in the INF file and you should be good to go. Please make sure to use altitudes that you picked (not allocated by Microsoft) ONLY for development and debugging, you NEVER should to release a filter or share it with customers without a proper altitude from Microsoft.
  • The InstanceFlags. The only documentation I've been able to find for these is in a presentation on Loading and Unloading Minifilters. In short, 1 means 'don't allow automatic attachments' and 2 means 'don't allow manual attachments'. I mostly use 1 during development anyway since I prefer to attach my filter manually to specific drives (generally by adding more lines to the script that loads the driver).
Since I mentioned altitudes here and the fact that you must register your filter with Microsoft and get an altitude from them, there is another process that requires a similar registration with Microsoft. I'm talking about the process of requiring a reparse point. Basically, if you call FSCTL_SET_REPARSE_POINT in your product (assuming you don't set any of the Microsoft reparse points, which are undocumented anyway as far as I know) you need a reparse point tag. There is a page that describes that process, Reparse Point Tag Request and the only other thing you'll need when applying for one of those is a GUID, which you must generate and then use together with the Reparse Point Tag (the one you get back from Microsoft) every time you call FSCTL_SET_REPARSE_POINT. (NOTE: this is only necessary for a very small set of filters and it's not related with the INF file at all, I'm just mentioning it here because it also requires a tag from Microsoft, similar to altitudes, so it's easy to do them both at the same time (if you are one of those filters)).

Anyway, hopefully this page and the links to MSDN documentation will get you going with your INF so that you can finally start working on the actual filter.