Thursday, February 11, 2010

Context Usage in Minifilters

I’m not sure why but in spite of there being pretty good documentation and even a sample available, the topic of how Contexts work and how filters should use them comes up a lot.

There are a couple of rules that govern contexts and pretty much everything follows from the interaction between these rules (this applies to all contexts). Please note that this is more of a design discussion and the implementation might be slightly different:

  1. When the reference count on the context gets to 0, the memory is freed.
  2. Any pointer to the context needs to have a corresponding increment on the reference count. This is done transparently when the filter requests the context via one of the functions (FltAllocateContext(),FltGetXxxContext(),FltReferenceContex() and even FltSetXxxContext() and so on).
  3. A context needs to be linked to the underlying structure (i.e. StreamContext  to the stream, StreamHandleContext to the FILE_OBJECT, VolumeContext to the volume and so on…; please note that we are talking from a design perspective, the implementation of exactly which structure has the pointer to the context might be different, but this is irrelevant for this discussion).

This is pretty much it. I’d like to walk through the most common scenarios and explain how those rules apply:

A filter allocates a context (FltAllocateContext) and it gets a pointer to a context (refcount 1). The context is not linked to anything at this point in time. If the filter calls FltReleaseContext, the refcount will drop to 0 and the context will be freed. If the filter tries to attach the context to the structure (say by using FltSetStreamContext – i’ll use StreamContexts for the rest of the discussion, and the underlying structure in this case is the SCB (Stream Context Block; or, for file systems that don’t support multiple streams per file (aka alternate data streams), the FCB)), then there are three cases:

  1. It succeeds. refcount is now 2, one for the link from the SCB and the other one is the one the filter has. 
  2. It fails and the filter doesn’t get a context back (for whatever reason: memory is low or the filter passed a NULL pointer for OldContext or there is some other failure). In this case there is still only one pointer, the one the filter has, so the refcount needs to be 1.
  3. It fails and the filter gets another context back (there already was a context attached and OldContext was not NULL). Now the filter has two contexts, the original context that it has allocated which has a refcount of 1 (only the filter has a pointer to it) and a new context (though the name is OldContext), with a reference count of at least 2 (because there are at least two pointers to it, one from the underlying structure, the SCB, and one that was just returned in OldContext so the filter can use it – there could be other references from other threads, but to keep things simple we will ignore those). The filter will need to release the original context it has allocated because it can’t use it (and since the refcount was 1 this will drop it to 0 and will free it). The filter will also need to eventually release the reference it got on OldContext, after using it (which will drop it back to 1, which represents the pointer from the SCB to the context).

Before we go any further i want to discuss what a filter can do when getting a context fails for whatever reason (this includes allocation failures and failing to set or get the context). Some filters can simply ignore that object (for example, a filter trying to log file IO might make a note in the log that IO to file X will not be logged and that’s that). Other filters might work in a degraded mode (for example, an anti-virus filter that is trying to be smart about scanning a file when it’s closed might want to remember whether there was any write to the file. If it fails to get a context it might scan the file anyway… performance might be worse but it will still work). And yet another case is where a filter might simply not be able to work when it doesn’t get a context. In that case the filter might want to allocate and initialize the context early enough so that the operation can be failed, usually in the Create path so in case the allocation fails the filter can fail the Create and the file won’t be opened at all.

Yet another thing to mention is that if a filter needs to use a context at DPC (let’s say in postWrite) then the context needs to be allocated from nonpaged pool and since the context functions are not callable at DPC the recommended way is to get the context (which might involve allocating it and attaching it) in the preOperation callback and pass it through the CompletionContext to the postOperation callback which can use it and then call FltReleaseContext to release the reference (yeah, even at DPC if the context is allocated from nonpaged pool).

One might wonder why the strange dance with the OldContext and NewContext. Couldn’t the filter just check if there is a context and only allocate one if there isn’t one ? Well, of course it could but because the environment is asynchronous, multiple threads might be doing the same thing at the same time, and they will all check if there is a context, find none, allocate a context and set it so now you have 10 threads each trying to attach an SCB with a different context… So the context operation needs to be a CAS (CompareAndSwap) so that only one thread succeeds.

Thus filter should not really start using a context that it allocated until it actually manages to attach it to the structure. However, immediately after the context is attached another filter might get to it, so it needs to be in some defined state, otherwise the other filter will get an invalid context (more on this later). The steps need to be something like this (this is pretty much the logic in CtxFindOrCreateStreamContext in the ctx sample in the WDK):

  1. context = FltGetStreamContext()..
  2. If we didn’t get one:
    1. context = NULL
    2. FltAllocateContext (NewContext)
    3. Initialize context to whatever default values make sense. Please note that those values need to take into account the current reference as well.. I’ll explain more below.
    4. FltSetStreamContext(NewContext, OldContext)
    5. If it failed:
      1. FltReleaseContext(NewContext) –> no point in keeping it around. Since we had the only reference refcount was 1 and it dropped to 0 so it will be freed.
      2. If we got OldContext, context = OldContext
      3. else, we didn’t get OldContext but we also couldn’t attach our context for some reason – the filter needs to continue without a context, whatever that means… (and no, KeBugCheck is not a good idea :)… )
    6. if it didn’t fail –> context = NewContext
  3. At this point context points to the context to use. If it is null, something went wrong and we should bail… (we could bail here or in 2.5.3., doesn’t matter). By bail i mean we should either fail the operation or popup a warning to the user or mark somewhere that we missed one so the results are not reliable anymore.. doesn’t matter.
  4. do things with context….
  5. FltReleaseContext(context) -  here we release our context. We can do this later, for example if we get the context in the PreOperation callback we might want to pass it via the CompletionContext to the PostOperation and release it there. Or we could queue some work item and pass it in a context and have the work item release it. Anyway, once the context gets release the reference count on the structure will drop back to 1 (for the link from the SCB to the context).

In step 2.3. i said that the context needs to be initialized to whatever values make sense, but IT MUST take into account the current reference. Well, this is not always needed but it depends on the particular design of the filter (it’s usually needed though so keep reading). Consider a filter that uses a StreamContext to keep track of how many threads it has doing IO on a Stream and is using a handle that the filter opened via FltCreateFileEx2. Let’s say that when the count gets to 0 the filter will call FltClose on the filter’s handle. Now let’s imagine a case where in step 2.3. the filter simply initializes the count to 0. The logic would be something like this:

  1. context = GetStreamContext(); // allocate new context or get the existing one. also get a handle by calling FltCreateFileEx2(…) if needed
  2. context->Count++
  3. Do things on context->Handle
  4. context->Count--;
  5. If (context->Count == 0) then FltClose(context->Handle).
  6. FltReleaseContext(context);

Do you see the problem here ? What happens if there are two threads, T1 and T2, and T1 allocates the new context, initializes so that context->Count is 0 (which means, it is initialized to a default value that doesn’t take into account the current reference) and then it sets the context (refcount is 2, 1 for T1 and one for the underlying SCB), before getting to step 2. it gets preempted by T2, which starts at the top. T2 will get a context (refcount is 3, 1 for T1, 1 for T2 and one from the SCB), it will increment the count (so context->Count is 1), it “does things”, then it decreases the count in step 4. (so context->Count is now 0) and then step 5. will proceed to close the handle. Step 6. will release the context (so refcount drops back to 2). Then when T1 resumes it will be at Step 2. and it will again increase the context->Count to 1 (from the wiki link above, this is a manifestation of the ABA problem), then it will do things on context->Handle which has been closed….. And there you have it… This could have been avoided if GetContext() actually initialized the newly allocated context Count to 1. This complicates things a bit because Step2. now might need to only be called when the context was not allocated in this thread, meaning that Step2. will probably need to move in GetContext() and so on..

Another thing worth mentioning is that once the underlying object is torn down, the link from it to the context will be broken (i.e. the pointer from the underlying object will go away), so the reference count will need to be decremented. In most cases where there are no outstanding references (there are no other threads using the context) the refcount will go to 0 and the context will be freed (and the filter’s context teardown callback will be called, if one was registered). There are a couple of implications this has. If a filter simply allocates a context, associates it with an object and then calls FltReleaseContext() (which is the normal way to set up a context), the filter doesn’t need to do anything else to make sure the context goes away. It will be torn down when the underlying object is torn down.

The other thing that follows from the fact that the context is tied to the lifetime of the underlying object is that if a filter can never leave the context in a bad state assuming that it will go away, because the underlying object might hang around for a while and get reused, reviving the context. For example, for a StreamContext where a filter has pointer to an ERESOURCE and an open count, it would be a mistake to free the ERESOURCE when the open count gets to 0 under the assumption that once the last handle goes away the SCB will go away as well, because that might not be true. The file system might cache the SCB and if a new open to the same files comes along the file system will reuse the cached SCB, which means that the filter will get a context that has an invalid pointer to an ERESOURCE. So in this case the right place to free the ERESOURCE is in the context teardown callback.

Finally, the last thing i want to mention is what FltDeleteContext does. FltDeleteContext unlinks the context from the underlying object. So if a filter decides it no longer needs a context associated with a stream (for example), it will need to do something like this:

  1. context = FltGetStreamContext();
  2. If (context != NULL)
    1. FltDeleteContext(context);
    2. FltReleaseContext(context);

At this point it should be obvious that FltDeleteContext needs to be called before FltReleaseContext (because since FltReleaseContext will release the reference associated with the context it is not safe to use context at all after calling it; FltDeleteContext will only remove the reference from the underlying object, if it is set, not doing anything about the current reference). Please note that after FltDeleteContext unlinks the StreamContext, any thread trying to get it from the object will not find it. This means that the filter should not try to use it in a meaningful way since other threads might not see the changes. Basically, once FltDeleteContext was called, the filter should simply call FltReleaseContext…

I hope this makes sense. If nothing else it should be useful when you have trouble sleeping (i almost fell asleep twice while proofreading it)

Verifier Checks: A filter has completed an operation but does not implement name provider callbacks

FILTER VERIFIER ERROR:   A filter has completed an operation but does not implement name provider callbacks. For example if a filter completes an IRP_MJ_CREATE and is not a name provider it is possible that during name queries the FO will make it's way to the file system and bugcheck. (Filter = Xxx, Cbd = Xxx)

This is a new check in Win7 and I get a lot of questions about it. In order to understand what it means we should talk briefly about how FltGetFileNameInformation and friends work.

In order to normalize a file name Filter Manager will get a path to a file in a number of different ways, including by querying the file system for it, and then it will open directories on that path and query for long names. In order to optimize this as much as possible (as you can imagine this is rather time consuming… incidentally, a minifilter should use opened names where possible as the perf is better) the decision was made to implement a separate mechanism from the normal IO path. Minifilters that change the path in any way should implement some additional callbacks, which we refer to as “name provider callbacks” and filter manager will call those callbacks when it needs to generate and normalize a name. If a minifilter does implement those then it is called a name provider. The best part about this is that if a minifilter is not a name provider then it can stay out of the name resolution path completely. If none of the minifilters on a volume are name providers then filter manager can skip all of them when it tries to resolve a name and go directly to the driver below.

How does this tie into the verifier check above ? If filter manager sees a filter successfully complete any operation that might have an impact on the namespace (like a IRP_MJ_SET_INFORMATION with FileRenameInformation or FileLinkInformation or FileShortNameInformation and so on) then it expects that the minifilter implements the name provider callbacks as well. Now let’s say that the minifilter successfully completed a rename for file “A” to file “B”. If the minifilter in question does not implement name provider callbacks, filter manager will not even ask it for the name. It will go straight to the name provider below (or the file system) and it will get back the old name , “A”. This clearly breaks the abstraction that each filter must implement.

There is one more operation other than those that change names that is particularly important. If a minifilter successfully completes IRP_MJ_CREATE (in this particular case returning STATUS_REPARSE does not count as success) then the FILE_OBJECT should NEVER under any circumstances be seen below that minifilter (the file system will try to interpret the private fields in it as its own, which can lead to bugchecks or data corruption). So if a minifilter successfully completes a create but doesn’t implement name provider callbacks then name queries will bypass it completely and the FILE_OBJECT will be shown below.

So now that we explained all the context, let’s go into what’s allowed and what’s not:

  • a minifilter can always fail any operation, including IRP_MJ_CREATE and IRP_MJ_SET_INFORMATION without implementing name provider callbacks. Also, completing an IRP_MJ_CREATE with STATUS_REPARSE counts as a failure in this context, even though STATUS_REPARSE is a success code.
  • if a minifilter successfully completes an IRP_MJ_CREATE (excluding STATUS_REPARSE), even if it doesn’t change the name space at all (like an SFO (shadow file object) type of filter) it must still implement name provider callbacks, even if they do nothing and are only passthrough (meaning that they get the name from below by calling FltGetFileNameInformation and return it). This minifilter is called the owner of the FILE_OBJECT.
  • if a minifilter implements any type of name space virtualization (which means that if there is any difference at all between the namespace above the minifilter and the namespace below the minifilter) then it must implement name provider callbacks and it must also implement (or at least make a conscious decision not to support some of the features) all the other name support operations, like directory enumeration, setting and querying short names, directory change notification, dile IDs, renames, hardlinks, reparse points and so on. This is true even if the minifilter does not own any file object (i.e. it never completes an IRP_MJ_CREATE but rather it just changes where files are on the file system).
  • a minifilter should NEVER skip any portion of the IO stack because there is no way to know if the FILE_OBJECT it cares about belongs above or below the filter. For example, a minifilter should never take a FILE_OBJECT and do something like allocate an IRP and send it directly to the file system below, since it might bypass the owner of that FILE_OBJECT. A far more common scenario of this same behavior is a minifilter that receives a HANDLE from its user mode component and it calls ObReferenceObjectByHandle and then it call some FltXxx function with that FILE_OBJECT, without realizing that the owner of that FILE_OBJECT might be above itself.

I hope this makes sense. As always, if you have any questions please don’t hesitate to ask.

Names and file systems filters

Proper usage of names in file system filters and minifilters is a topic that comes up a lot. The reason for this is that sooner or later one has to deal with names and it is a particularly complicated area. In this post I’ll try to address some of the common problems minifilters have with names and suggest some ways that can be used to achieve some of the common scenarios.

There are a couple of factors that make names hard to work with:

1. Computer users are used to names. They think they understand file names. They might not understand many other things, but the fact that a file has a name is something that is pretty clear to everyone. This affects file system filter developers in two ways. First, all developers started as users and as such they suffer from thinking in terms of names in cases where they shouldn’t. Second, most file system filters are meant to be used by users (or written according to user specifications) and as such they need to work with names, because that’s what users know and want.

2. File systems don’t really care about names. I mean, they need names to talk to users, but file systems developers spend most of their time thinking about improving IO performance or reliability and in general about things that happen after a file is created (names matter to file systems pretty much only in the create path). File systems don’t care whether your file is a word document or not, but filters sometimes need to know and names play into this.

3. There are so many of them. You have long names and short names and then file IDs object IDs. And then you have hardlinks and symlinks. Also, there are alternate data streams that also have names. And then you have remote file systems where one needs to care about machine names and redirector names and so on. An additional problem is that users don’t usually get the finer details about all these, so specs they come up with often don’t properly address all the possible interactions.

4. The IO stack in Windows is asynchronous. Which means that a name can change at any time. Which in turn means that once a filter gets a name, it might already be useless or wrong. Sure, one can argue it’s a rare occurrence and regular users wouldn’t run into it. But what about malicious users ? You are unlikely to run into a race between renames and transactions on a regular user’s machine, but what if someone makes it happen ? A real product can’t afford to ignore such cases.

Now let’s take a look at some of the things minifilters try to do with names. It turns out that there aren’t that many. In fact, there are pretty much three types of things that minifilters do with names. Each of these classes has specific requirements which I will address at length:

1. Open files by calling FltCreateFile. This is done to scan the file or to read or write contents, to encrypt it or something like that. Once the file has been opened there usually is a handle that is used in subsequent operations so the name is not interesting anymore. Things to note here are:

  • the filter must know the name of the file at this moment. If the filter is trying to open a file that has been renamed, it needs to know the new name. Opening the file by the old name might lead to problems.
  • FltCreateFile can only be called at PASSIVE_LEVEL.

2. Send the name to user mode for some reason (to display it to the user, or to open the file in user mode, or to log operations and so on). The vast majority of these operations are not synchronous (i.e. there is no operation that is blocked in kernel mode waiting for the user to read the message). There is one common exception, which is Anti-virus software which in case it finds a virus will sometimes prompt the user for action and it needs to display the file name (it will also probably log the name, but that can be done asynchronously). The reason this is important is that by the time the name is consumed (the user reads the log for example) the name could very easily have changed. Things to note:

  • the name of the file is usually less important. If a filter logs writes, if a rename happens at the same time writes are happening, the order doesn’t usually matter much.
  • because the information is meant to consumed by the user, performance and lag in presenting the information doesn’t matter. Even in the AV case, where the user must chose some action before the kernel thread can continue, the user is much slower than the processor. So for these types of scenarios performance is not usually important (in these paths at least; overall performance impact of the product is a different issue).

3. Policy checks. This is usually done in an effort to understand if a file is interesting or not to the filter. This is usually the case where the is some policy that is enforced by the user. For example and anti-virus filter might ignore files under a certain path or an encryption filter might only encrypt .doc and .txt files. Key things here:

  • it is a bad idea to check if the file is interesting by querying and parsing the file name every single time the filters needs to know this. A better design is to cache the information about the file somewhere and then update it only when it changes. Since we are talking about name based policy here, the only place where it can change is in the rename path. Stream contexts are particularly suited for this task and what filters normally do is attach a stream context if the file is interesting. Then, when they need to decide whether the file is interesting or not they can simply get the stream context and if one is present then it is interesting.
  • The stream context is initialized at create time and is potentially changed at rename. Both these operations happen at PASSIVE_LEVEL. Some filters prefer to query the name when the operation they care about happens, but this approach usually generates more problems that it solves.

4. Virtualization. Minifilters will use names to create a virtual namespace (inject virtual files or folders into the file systems namespace or hide files). This has a different set of challenges (many information classes that expose names, directory change notifications, oplocks and so on) but querying names is fairly easy. Also, the minifilter either is the owner of part of the namespace, which means it can serialize things and it is in the position to authoritatively know what the name of the object is or it is hiding part of the namespace, which means there will be no operations on that part of it (since no one knows it’s there).

Now that we have all the pieces in place, let’s look at some of the common scenarios.

By far the most common failure is to try to get a name where it is not supported. Like at DPC or in the paging path (people want names when writes happen to a file). This has in the past made people believe that the name support in filter manager (via FltGetFileNameInformation and friends) is broken. However, that is not the case. The important thing to understand in this case is that it is almost never the case that the name is actually needed in these cases. And by needed I’m referring to how the name is going to be used. If it is a class 1 (looking at my classification above) operation (FltCreateFile) then if the name can’t be obtained FltCreateFile cannot be called anyway (by that i mean that if FltGetFileNameInformation can’t get the name then it is illegal to call FltCreateFile). For the 2nd class of operations, the approach is to queue an async work item to get the name for this file and use it (send it to user mode, log it to the file and so on). Remember that neither accuracy nor performance usually matter here so not waiting for the work item to finish is usually ok... For the 3rd class of operations it only matters if the context is not set up yet because once a context is in place the decision should be made based on it. However, the approach of getting the name the first time it is needed has some drawbacks like the fact that getting the name and setting the context can race with renames (outside of the IRP_MJ_CREATE path) so the name might become invalid immediately; also the lack of a context might mean that the file is not interesting as well as the fact that this is the first operation for a file…


Another common scenario is to try to open the same file the user has open. Some anti-virus filters do this to scan the files. So the minifilter gets the name of the file in pre or post IRP_MJ_CREATE and then tries to open it. This works in the sense that one can get the name both in pre and post create, but it is problematic because the name of the file can change (however AV scanners should avoid scanning in preCreate for other reasons…). It’s hard to come up with a scenario where a malicious file might end up on a user’s system by taking advantage of this, but even so it is something to consider. Another common scenario is to open an alternate data stream for a file the user has opened in a filter. The same set of issues around racing with renames applies. A solution for this is to use a rather unknown feature of the IO system, relative opens. For any ZwCreateFile or FltCreateFile when initializing the object attributes with the InitializeObjectAttributes macro there is a parameter that accepts a handle to the root directory so that a file can be opened relative to a directory. However, this can be used to solve the problems in the example above. If the name passed in to InitializeObjectAttributes is empty (the Length = MaximumLength = 0 and Buffer = NULL) then the create will open the same stream. So if a filter wants to open another file object for a stream the user has open (or an alternate data stream for the same file) then the filter can call InitializeObjectAttributes with a handle to the user’s FILE_OBJECT (one way to generate a handle is via ObOpenObjectByPointer) and use an empty name (to open exactly the same stream) or just the name of stream (to open an alternate data stream) as the ObjectName.


One more thing I would like to point out is that a call to FltGetFileNameInformation in preCreate might fail if the create itself will fail. So if FltGetFileNameInformation fails with a weird status in preCreate, please make sure to investigate if the user’s create would actually have succeeded. In such cases where getting the file name in preCreate is vital to the operation of the filter then the filter should most likely fail the user’s create if FltGetFileNameInformation failed. Generally it would be better it things were done in postCreate, where possible.


There are a lot more interesting things with names but these are some of the common things that filters try and have problems with. Feel free to ask questions about specific scenarios.

Issuing IO in minifilters: Part 2 – Flt vs. Zw

Sorry about the frequency of my posts, i’m been really swamped with the IFS Plugfest preparations.

Anyway, let’s get down to business. So now the way the create path works should be clear. The basic idea is that FltMgr has some targeting information (in which it stores the minifilters below which altitude should see the operation). In the create path the information is stored in an ECP (or, pre-Vista, an EA).

After the create operation has completed successfully, the targeting information is taken from the ECP and moved to a FILE_OBJECT context (private to filter manager). This allows filter manager to always figure out where IO to a file object should be targeted. This is very important. It means that once a filter opens a file with FltCreateFile, filter manager will always target the IO properly, at the minifilter below the one that created the file.

Another piece of the puzzle that I should mention is that IO manager uses a similar mechanism to the one filter manager uses to remember the device that was specified as a hint when a FILE_OBJECT was created. This information is used in IoGetRelatedDeviceObject to figure out which device the IO should be targeted at.

Finally, I need to address one very important concept: where a minifilter is sitting on the IO path. There are a couple of cases that are best described by example:

  1. A minifilter, being a device driver after all, can register for some notifications such as IoRegisterShutdownNotification or PsSetLoadImageNotifyRoutine. When these callbacks are called, the minifilter is not on the IO path at all. It should behave like any other driver in the system. So if it needs to do stuff like write something into a log it should issue IO to the top of the stack, using APIs like ZwCreateFile, ZwWriteFile and so on.
  2. Expanding on the example above, a minifilter can filter some volume (like the system volume) and write something (like a log) to another volume. In this case, while the minifilter is on the IO path on the volume it filters, it is not on the IO path for the volume it wants to write to. So, like in the case above, it might issue IO to the top of the stack on that other volume using APIs like ZwCreateFile, ZwWriteFile. This is good because it doesn’t require the minifilter to attach an instance to that volume. The two device stacks are pretty much unrelated.
  3. However, there are cases where a minifilter wants to write to the same volume. As we’ve already established that reentering the IO stack at the top is evil and the wrongdoers should be sent to Azkaban, the minifilter writer has no choice but to issue the IO below itself on the same stack. This is where the minifilter has two options:
    1. Use the user’s FILE_OBJECT. I use the term “user’s FILE_OBJECT” to refer to the FILE_OBJECT in FltObjects->FileObject, which might in fact be a FILE_OBJECT created by a filter above, not necessarily coming from the user. Anyway, the only way to do this is to call APIs that take an instance, so that filter manager can target it properly: FltWriteFile, FltQueryDirectoryFile and so on. Alternatively, feel free to allocate a CALLBACK_DATA using FltAllocateCallbackData, set up the parameters and then call FltPerformSynchronousIo. Filter manager will show the IO to the minifilters below that instance and then, if none of them complete the IO, it will allocate an IRP and send the request below. IO manager does not get involved in this at all (i mean, filter manager will call IoAllocateIrp and IoCallDriver, but the targeting logic in IO manager will not be used).
    2. Call FltCreateFile and get its own FILE_OBJECT. Once this create completes successfully, the minifilter can use Flt APIs or Zw APIs. The reason Zw APIs work well is because IO manager will use IoGetRelatedDeviceObject which will send the IO directly to filter manager’s device and then filter manager will find the targeting information associated with the FILE_OBJECT and will send it to the proper instance.
  4. Finally, there can be a case where a minifilter might decide to issue IO below its instance on a different volume (so the minifilter is injecting IO into the IO path on a different volume). Since using the user’s FILE_OBJECT is clearly not an option, it will need to create a new FILE_OBJECT for that second volume. However, to target below its instance it needs to call FltCreateFile (which takes an instance). However, from that point on, it can call either the Zw APIs or the Flt APIs on that file object and the operations will be targeted correctly.

Of course, there are many ways to implement something in a minifilter and both the IO model and filter manager allow for a lot of flexibility. So the list below should be treated more as a guideline and not like strict rules. Finally, here are the guidelines:

  1. Anytime you find yourself trying to call an API that requires an instance and you don’t have one, don’t try to hack something up. You’re probably not supposed to call that API from in that context.
  2. If you want to use the user’s FILE_OBJECT, you must use send the IO below yourself and you must use Flt APIs. If there isn’t one you can build your own with FltAllocateCallbackData and FltPerformSynchronousIo. However, using Zw APIs in this context will cause reentrancy.
  3. If you have your own FILE_OBJECT (meaning it was created with FltCreateFile), you can use either Flt APIs or Zw APIs. You don’t have to stick with one set, you can mix & match, depending on which provides more value.
  4. If you want to issue IO on a different volume, you can either use ZwCreateFile or FltCreateFile, depending on your design (an important factor in the decision is whether you have an instance on that volume). However, once the FILE_OBJECT and handle are created, the logic in the rule above applies.
  5. If you are completely outside the IO stack in some cases, you might be better off sending IO to the top of the stack (in those cases). What I have in mind here is a minifilter that does a bunch of other stuff like registering for PsSetLoadImageNotifyRoutine.
  6. If you need to issue IO to a different volume and you don’t have an instance to call FltCreateFile, it’s probably not a good idea to attach to that volume just to have an instance to pass into FltCreateFile. ZwCreateFile might be what you need. This is not a rule, you can create an instance just to inject IO in a different stack, but think hard about it before you do. It might not be necessary.

I hope this makes sense, but feel free to post any questions.

Issuing IO in minifilters: Part 1 - FltCreateFile

In this post I'll try to address a couple of questions that are all related and that I've seen asked a lot. This is a rather long topic so I'll split into a couple of posts. I’ll try to explain both how things work and why they are this way (at least, why I think they are this way…).

These are some of the questions that I'm trying to provide an answer to:

  1. How does FltCreateFile work ? How does a CREATE request find the next filter to send the IO to ?
  2. I don’t have an instance but I need to perform some operation a file and I'd like to use the FltXxx function.. how do I do this ?
  3. There is no Flt… function for what I want, what should I do ?
  4. Why shouldn’t a minifilter send IO to the top of the stack ? What does that mean and what happens if I do..
  5. Why is it a bad idea to hold locks across calls into the file system?

The fundamental issue here is that the file system stack in Windows is reentrant. What this means is that it is possible that in response to some IO operation there will be a new IO operation generated at some location in the IO stack (i.e. in a filter or in the file system) that will be posted at the top of the stack, which needs to complete before the original operation can complete. The critical thing here is that the new IO needs to complete before the original one. If a filter or the file system issues IO to the top of the stack but doesn’t wait for it to complete then the chance of getting into a deadlock is greatly reduced (it can still happen if the logic in some other components depends on the order in which the original IO and the second IO complete, but still it’s a lot less likely).

Reentrancy in the IO model

For this example I chose a filter to trigger the reentrancy but usually it is the file system that does this. Please note that the filter might be triggering this just by trying to access some memory that is paged out (in that case step 4 wouldn’t go directly to IO manager but rather memory manager but anyway you get the idea), so operations 4-8 are simply trying to page the data in.

So why is this bad ? Well, the main reason is that it’s very easy to end up deadlocking the system. If any of the filters happen to have a lock that only one operation can acquire then the second IO will block behind that lock and it will wait forever for the first IO to finish. This is a simple and clear example and one could try to imagine various schemes to prevent this from happening (like adding rules around when the lock is held or making the second IO aware that locks are held and make it not try to acquire the lock again and so on). This is in fact exactly what the file system does (since it holds locks while issuing IO to the top of the stack pretty often) and it all works well until something else gets in the way of that IO (usually a filter). This is why filters need to be extra careful about how they issue IO.

This is also one of the main reasons why it’s generally a bad idea to hold locks across calls to the file system. If there was no reentrancy then you have a guarantee that the operation you sent down will finish without trying to reacquire the lock. However, the way it is now it’s impossible to know all the possible paths, especially since filters always alter the semantics in subtle ways, which can mean new dependencies between operations that were previously unrelated.

Before we start talking about FltCreateFile I must point out that the usual way of issuing IO (allocate an IRP or CALLBACK_DATA, fill in the parameters, send it to the driver below and wait for it to complete – this does not require reentering the IO stack) does not work well for creates. Anyone trying to issue an IRP_MJ_CREATE this way would soon find out that they need to handle things like security checks, handle creation, name resolution, reparse point or symlink handling, the whole lifetime of the FILE_OBJECT and many other sensitive operations. The safest bet is to let the system handle the CREATE operation, which means the create operation needs to go to the IO manager so it can handle all these things. So any filter that needs to open a file needs to reenter the IO stack at the top.

Now, let’s think about how a filter can open a file. Let’s say this is an anti-virus filter and it wants to open the file before the user can open it and scan the contents. Let’s also assume that it wants to do this only in kernel mode (this is not usually what AV filters do, but let’s keep it simple). So the logic in its CREATE processing routine is simple:

  1. Get the current file name that the user is trying to open
  2. Open the same file (remember that this goes to the top of the stack)
  3. Scan it
  4. Close the file
  5. If the file is clean, let the original create go to the file system. Otherwise fail it.

Looks pretty simple on paper. The real question is, of course, how to open the file. Should it simply send the create to the top of the stack (and not worry about reentrancy)? Just doing that quickly results in an infinite loop (the new create comes down the path and the AV filter will see it and since it’s a create it needs to block it and send a new create which will come down the path etc…). So clearly the AV needs a way to identify that the create it sent to the top is its own create, so it shouldn’t block it (like filter 3 in the picture above). But how ? Pretty much any scheme it can use to tag this create somehow will be broken if there is another minifilter in the stack that does exactly the same thing. Imagine Filter 1 and Filter 2 are both AVs that work in the same way but they don’t know how about each other (two different products from different vendors if you will). What will happen is this:

  1. User’s create gets to Filter 1
  2. Filter 1 check for its tag (T1 – the tags can be anything the filter can do to mark a create, from doing something to the file name to setting some weird flag combination; also Filter 1 knows nothing about Filter 2 so it doesn’t look for it’s tag, T2, which we will talk about later) and since it doesn’t find the tag it issues a new Create (C1) which it tags and sends to the top of the stack.
  3. Filter 1 sees the new Create but it finds the tag T1 so it lets it go down.
  4. Filter 2 now sees a create, checks for its tag (T2; as I said before, Filter 2 knows nothing about how Filter 1 works so it doesn’t know to look for T1) which is not set, so it blocks the create and it sends a new create (C2) with the T2 tag set to the top of the stack. In this process T1 might get lost or overwritten by T2 (assume they use exactly the same way to tag things… ). Goto 2.
  5. Actually, step 5 will never execute…

So here you have another way where reentrancy can mess things up. This should also explain why tagging operations in a certain way and sending them all the way to the top can fail if you don’t know what the filters above yours do.

Clearly, it would be better if the filter could simply send the creates below itself, to the rest of the IO stack, while still using the IO manager’s code. Of course, if any other filter misbehaves and sends a new create to the top of the stack things can get ugly, but at least if all filters work well this model will actually work. Fortunately, IO manager provides a routine to do just that: IoCreateFileSpecifyDeviceObjectHint, which opens a file through IO manager while skipping some devices on the stack. So this takes care of the legacy file system drivers. They simply should call IoCreateFileSpecifyDeviceObjectHint and target the create at the next device below themselves.

What about minifilters and FltCreateFile? Please note that the other functions (FltCreateFileEx and FltCreateFileEx2) work the same way, so I'm talking about all three of them. Well, as you may have noticed, FltCreateFile takes an FLT_INSTANCE parameter. This parameter, when present, indicates to Filter Manager that the Create should go only to instances below that instance. Of course, if the instance is missing then the Create will go the top of the stack. So when issuing the create Filter Manager needs to tag it with some information which it can then use (when it sees the create come down the stack) to figure out which minifilters should see it.

Now, let’s talk a bit about how FltMgr can tag the create. Starting with Vista there is a mechanism that allows filters to add extra information to creates. The structures that are allocated to hold this information are called ECPs (Extra Create Parameters) and each create operation can have an arbitrary number of such ECPs that are all attached on an ECP_LIST. There is a whole set of APIs to allocate the list and the ECPs (start by reading documentation for FsRtlAllocateExtraCreateParameterList or FltAllocateExtraCreateParameterList). Before Vista filters would achieve some of the same functionality (but not all of it, the ECP model is more powerful) by using Extended Attributes (look at IoCreateFile and IoCreateFileSpecifyDeviceObjectHint, they take an EaBuffer parameter). However, for filter manager’s purposes, EAs are good enough.

Now that we have all the elements we can finally answer the first question in the list. FltCreateFile will allocate an internal targeting structure and store it in an ECP (or, before Vista in an EA) and then call IoCreateFileEx and specify its own device (based on the instance that is passed in) as the hint. Then while processing any create operation it checks for its ECP (or the EA), it gets the targeting information structure from there and from that structure it figures out where the IO needs to go. If there is no targeting information then the assumption is that this IO was not targeted so filter manager will simply show the IO to all minifilters. So at this point the DeviceObject hint takes care of the legacy filters above the target frame (including other filter manager frames) and the targeting information tells it which minifilter should be the first to see this create.

In this post I’ve addressed questions 1, 4 and 5. The next post will tackle 2 and 3.

Filter Manager Concepts: Part 7 – IRP_CTRL

One very important structure that everyone writing minifilters very quickly becomes familiar is the FLT_CALLBACK_DATA. This is pretty much the equivalent of an IRP in the minifilter model. The structure is public and is pretty well documented. However, it is in fact just the public part of the picture. Filter manager has an internal structure (the IRP_CTRL) that wraps the FLT_CALLBACK_DATA. So there is a one-to-one relationship between the IRP, the IRP_CTRL and the FLT_CALLBACK_DATA. Let’s see that !fltkd tells us about it:

3: kd> !fltkd.irpctrl fffffa80075ef7d0
IRP_CTRL: fffffa80075ef720  CREATE (0) [00000009] Irp SystemBuffer
Flags                    : [1000000c] DontCopyParms Synchronize FixedAlloc
Irp                      : fffffa8003e6dc60
DeviceObject             : fffffa8004603be0 "\Device\HarddiskVolume1"
FileObject               : fffffa8008437070
CompletionNodeStack      : fffffa80075ef870   Size=2  Next=1
SyncEvent                : (fffffa80075ef738)
InitiatingInstance       : 0000000000000000
Icc                      : fffff8800244f5b0
CreateIrp.NameCacheCtrl  : 0000000000000000
CreateIrp.SavedFsContext : 0000000000000000
CallbackData             : (fffffa80075ef7d0)
 Flags                    : [00000009] Irp SystemBuffer
 Thread                   : fffffa8006b9cb60
 Iopb                     : fffffa80075ef828
 RequestorMode            : [01] UserMode
 IoStatus.Status          : 0x00000000
 IoStatus.Information     : 0000000000000000
 TagData                  : 0000000000000000
 FilterContext[0]         : 0000000000000000
 FilterContext[1]         : 0000000000000000
 FilterContext[2]         : 0000000000000000
 FilterContext[3]         : 0000000000000000
   Cmd     IrpFl   OpFl  CmpFl  Instance FileObjt Completion-Context  Node Adr
--------- -------- ----- -----  -------- -------- ------------------  --------
 [0,0]    00000000  00   0000   0000000000000000 0000000000000000 0000000000000000-0000000000000000   fffffa80075ef8f0
            Args: 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000
 [0,0]    00000884  00   0000   fffffa800623a010 fffffa8008437070 fffff88004e5e474-0000000000000000   fffffa80075ef870
            ("luafv","luafv")  luafv!LuafvPostCreate
            Args: fffff8800244f750 0000000001200000 0000000000070000 0000000000000000 0000000000000000 0000000000000000
Working IOPB:
>[0,0]    00000884  00          fffffa800623a010 fffffa8008437070                     fffffa80075ef828
            Args: fffff8800244f750 0000000001200000 0000000000070000 0000000000000000 0000000000000000 0000000000000000
3: kd>

As you can see, a lot of information. The first thing I’d like to point out is there is another extension you could have used to display exactly the same information, !fltkd.cbd. In fact, they are exactly the same function internally and you can pass in the address of an IRP_CTRL or a FLT_CALLBACK_DATA and it’ll figure out what it is and display it. Also, very useful in debugging is the flag ‘1’. I didn’t use in this because it tends to generate a lot of output but try it yourself and you’ll see it displays a lot of good stuff.

This structure is central to filter manager and it is pretty much where the vast majority of my debugging sessions start. Some of the very interesting things you can get using this listing are, of course, the IRP, the InitiatingInstance (who initiated this IO? does it come from the user or was it a minifilter ?) and the Flags of the IRP_CTRL itself.

Filter Manager Concepts: Part 6 – STREAM_LIST_CTRL

Now that we’ve discussed contexts in general there is one very important structure to talk about. The STREAM_LIST_CTRL is pretty much filter manager’s context for a stream (it is attached to the FCB or SCB, depending on the file system) and it is used to store stream contexts, streamhandle contexts and file contexts (for file systems where a file can only have one stream; otherwise there is a different structure dedicated to those contexts) for minifilters as well as the name caches for the stream. The “!fltkd.streamlist” command displays a STREAM_LIST_CTRL and it can take either a STREAM_LIST_CTRL or a FILE_OBJECT address.

Because filter manager can be attached multiple times to a stack it also has multiple contexts, so it is possible that a FILE_OBJECT has more than one STREAM_LIST_CTRL associated with it.

0: kd> !streamlist 0xfffffa80`039aff20 f
STREAM_LIST_CTRL: fffffa8005ffad30  [00000111] LinkedToStream File HasHardlinks
   Stream ContextCtrl       : (fffffa8005ffad38)
   VolumeLink               : [fffffa800563f580-fffffa8003ee2260]
   UseCount                 : 2
   ContextLock              : (fffffa8005ffad78)
   StreamContexts           : (fffffa8005ffad80)  Count=1
      CONTEXT_LIST_CTRL: fffffa8005ffad80
         CONTEXT_NODE: fffff8a00265a5b0  [0008] StreamContext PagedPool
            ALLOCATE_CONTEXT_NODE: fffffa80044ec6d0 "FileInfo" [01] LookasideList (size=80)
            AttachedObject           : fffffa8005ffad30
            UseCount                 : 2
            TREE_NODE: fffff8a00265a5c8 (k1=fffffa80054cbbb0, k2=0000000000000000) [00010001] InTree
            UserData                 : fffff8a00265a610
   StreamHandleContexts     : (fffffa8005ffad88)  Count=0
      CONTEXT_LIST_CTRL: fffffa8005ffad88
         ** Empty tree **
   NameCacheLock            : (fffffa8005ffad90)
   AllNameContextsTemporary : 0x00000000
   LastRenameCompleted      : 0x0000000000000000
   NormalizedNameCache      : (fffffa8005ffada0)  Count=1
      NAME_CACHE_LIST_CTRL: fffffa8005ffada0  [00000001] Normalized
         NAME_CACHE_NODE: fffff8a001f0b470
            UseCount                 : 2
            CreationTime             : 0x00000000003ae3e1
            TREE_NODE: fffff8a001f0b488 (k1=fffffa80039aff20, k2=0000000000000000) [00018000] InTree
            FLT_FILE_NAME_INFORMATION: fffff8a001f0b4c0  [00000001] Normalized
               NamesParsed              : [00000000]
               Name                     : "\Device\HarddiskVolume1\Program Files\Common Files\System\Ole DB\sqloledb.rll"
   OpenedNameCache          : (fffffa8005ffadc0)  Count=0
      NAME_CACHE_LIST_CTRL: fffffa8005ffadc0  [00000002] Opened
         ** Empty tree **
   ShortNameCache           : (fffffa8005ffadb0)  Count=0

This shows you for example that the there is only one context on this FILE_OBJECT and it’s actually a StreamContext from FileInfo. Also, there is a normalized name.

This sort of information can be really useful when trying to determine if your minifilter has a context on a certain stream or FILE_OBJECT or to find out more information about a leaked context or name (such as what stream or FILE_OBJECT was it for).

The “!fltkd.streamlist” command is pretty much what I use this any time i have a FILE_OBJECT.

Filter Manager Concepts: Part 5 – CONTEXT_NODE

Why does one need contexts ? Well, the IO model in NT is based on passing objects around and the various components that handle these objects need a way to save information about each object (for example the file system might need to ‘remember’ where the file is located on disk). This information is basically “context” for that object. Naturally, there might be multiple components that process a certain object and so each object has multiple context associated with it.

Support for contexts in NT in general appears to have been done in a pretty ad hoc manner. For example, some components in the system use the object itself to store their context (for example the IrpList in the FILE_OBJECT). Some other components use special fields in the object (the FsContext in the FILE_OBJECT is where a file system can put a pointer to its context, known as the FCB or SCB). Some objects have an “extension” where whoever allocates it can store their context (like DEVICE_OBJECT and the extension). And finally some objects allow storing and retrieving any structure (FSRTL_ADVANCED_FCB_HEADER and FsRtlInsertPerStreamContext). As you can see this is quite messy and filter manager’s abstractions make things somewhat more consistent for minifilters. These abstractions are pretty well documented here. There is also a sample in the WDK, “ctx” which shows how to call the APIs and all that.

From looking at the APIs one can see that the user contexts are declared as PFLT_CONTEXT. However, this is not a structure at all, it’s simply an alias to PVOID. The real structure that stores the information about the context is CONTEXT_NODE:

0: kd> !fltkd.ctx fffff980075dac30
CONTEXT_NODE: fffff980075dac30  [0002] InstanceContext NonPagedPool
   ALLOCATE_CONTEXT_NODE: fffff98007a8cd80 "luafv" [01] LookasideList (size=968)
   AttachedObject           : fffff98007aa64c0
   UseCount                 : 1
   TREE_NODE: fffff980075dac60 (k1=fefefefefefefefe, k2=fefefefefefefefe) [00010000] InTree
   UserData                 : fffff980075daca0
   UserDataSize             : 856
   FilterLink               : [fffff98007b9e9b8-fffff98007b9e9b8]

UseCount is the number of references for the context. If it gets zero then the context will be freed. All contexts have an initial reference, which is the reference from the underlying structure (FLT_INSTANCE in this case) so UseCount is usually 1. When the object goes away this reference is released and the context goes away as well (if there aren’t any other references). This reference represents the link from the object to the context (FLT_INSTANCE for example has a member “Context” that points to the context, while other structures have a CONTEXT_LIST_CTRL structure to which contexts are attached).

I sometimes get asked about the difference between FltDeleteContext and FltReleaseContext. FltDeleteContext releases the “initial” reference, from the underlying object (and also removes the link from that structure to the context so from that point on any function to Get the context will not find it). The caller of FltDeleteContext must have a valid reference (since it takes a PFLT_CONTEXT parameter) so the reference count before calling FltDeleteContext is at least 2. After FltDeleteContext releases the reference from the underlying object the context is still valid and the caller still has a reference to it (but it’s not linked to the object anymore). Once the caller calls FltReleaseContext the context might go away (and it will go away if it was the last reference). So if someone wants to delete a context they must call FltDeleteContext and then FltReleaseContext, in this order.

Another thing to note is that the context is linked into two different structures: the underlying structure that it is attached to (usually through TREE_NODE) and the filter that allocated it (through the FilterLink member).

The deal with LUAFV.SYS

I noticed that a lot of the people that end up on this blog are looking for information on LUAFV and for some reason it seems there isn’t a lot on it. I imagine that people are looking for it for two major reasons. They want to know what it is and what it does or they want to know how to disable virtualization for a certain application. There are some posts that do a pretty good job of describing what it does, but to find them you need to search for “UAC file virtualization”. Anyway, i’ll try to address all these issues.

A lot has been written and discussed (and flamed) about UAC (aka LUA) but i’ll go over the basics one more time. There are a lot of applications that require the user to be an administrator on the machine for no good reason (the same way some online services ask for your home phone number or SSN.. you know they don’t really NEED it.. ). This used to be a pretty common thing a while back (I remember even IM applications that needed administrative privileges ?!) and the end result is that the machine is a lot less secure that it needs to be. So it is in some way related to security, but it’s not a security feature (though why the icon is a shield is beyond me.. MS must have had some leftover shield icons or something). Anyway, in order to ‘fix’ this without breaking backwards compatibility MS needed a couple of things:

  1. a way to make a normal user elevate to administrator (the UAC prompt)
  2. a way to make applications that want to write data (files or registry) to a system location write it to a user location instead
  3. possibly a way to ‘encourage’ app writers to not do this anymore (the unintended side effect of annoying the users should have worked, but people like their scapegoats and so they blame Microsoft and not the app writers..)
  4. get all this without changing existing apps!!

Well, LUAFV.SYS is the component that implements the second thing on the list. It silently (the application doesn’t know) redirects file operations from protected locations to locations in the user’s path. So when the application tries to write it’s configuration to “c:\windows\system32\lame_app_config.ini”, it is redirected in some directory somewhere under the “Users\<user_name>\AppData\Local\VirtualStore\” path (i think that’s the path, but if you need a definitive answer you need to search some more). It is part of the operating system since Vista and it ships in the box (as far as i can tell, I’m not familiar with all the versions that get shipped) on both server and client.

My first encounter with LUAFV was a while back when i was using the BEST file manager known to man (and possibly dolphins and other species), FAR, to write some configuration file to some protected system location to be used by a service. I was writing to the file and my service seemed to ignore everything i wrote… It was a pain to figure out and it drove me crazy until i remembered about LUAFV. It turns out far.exe was virtualized, while the service was not (so FAR wrote to my user path while the service read the real path). To check that an application is virtualized (and even to change it from being virtualized to not being virtualized) one can use the Task Manager:


Please note that the “User Account Control (UAC) Virtualization” column is not shown by default, you need to select it from View->Select Columns.

So once you found that your process is virtualized (and you probably don’t want your file manager virtualized) it’s an easy fix disable virtualization. But what if you never want a certain process to be virtualized (like FAR in my case) ? Well, it seems you need to create a manifest file and embed it in the executable (you might not need to embed it for it to work, but i did it anyway because i don’t like keeping extra files around). Manifests are not my strong suite, but all the information you need (and more) is available from MSDN if you search for “Create and Embed an Application Manifest”. I also found a very short tutorial here. The manifest that i created for far.exe looks like this:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity version="" name="Far.exe"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <requestedExecutionLevel level="asInvoker" uiAccess="false"/>

I hope this helps.

Filter Manager Concepts: Part 4 –FLT_INSTANCE

The next logical structure in the hierarchy is the FLT_INSTANCE. An instance represents an attachment of a filter on a volume. Please note that any given filter may have more than one instance on a given volume, at different altitudes. This is something pretty unusual but the system allows it. I’ve used it when I wanted to monitor something between each installed minifilter.

Another case when this might come in handy is when developing a minifilter: a really good way to test it is to have multiple instances of it one below the other. This is unbelievably useful at revealing strange errors and unfortunate design decisions. And for those design decisions you don’t want to wait until your product is ready to ship so better start early on with this… It might require some effort to do this at first (including instrumenting your minifilter) but you’ll probably find some issues that would otherwise remain hidden until you interop with some other minifilter at PlugFest or until a customer finds it for you.

One important point is that all the instances of a minifilter belong to the same frame (I already mentioned this while talking about FLT_FILTER but this question seems to come up again and again). Given that having multiple instances on the same volume doesn’t appear to be that useful this is not a problem in general, but I still got bitten by it a couple of times.

Let’s see if i can fit more stuff in the picture:


So, just looking at the picture, we can tell that in Frame 2 (the one on the top) all the instances belong to one filter. It’s hard to tell why there are 3 instances of the same minifilter on the FLT_VOLUME associated with \Device\Mup, but you know, it can potentially happen.

Now, in the debugger you’ll see this (which, BTW, is completely unrelated to the picture above):

0: kd> !fltkd.filters
Filter List: fffffa80022fa0c0 "Frame 0"
   FLT_FILTER: fffffa800340b310 "luafv" "135000"
      FLT_INSTANCE: fffffa800340c010 "luafv" "135000"
   FLT_FILTER: fffffa80023009c0 "FileInfo" "45000"
      FLT_INSTANCE: fffffa800249cbb0 "FileInfo" "45000"
      FLT_INSTANCE: fffffa80028fc680 "FileInfo" "45000"
      FLT_INSTANCE: fffffa8002d40100 "FileInfo" "45000"
      FLT_INSTANCE: fffffa8002d663f0 "FileInfo" "45000"
      FLT_INSTANCE: fffffa8002910bb0 "FileInfo" "45000"
      FLT_INSTANCE: fffffa8001ac4bb0 "FileInfo" "45000"
      FLT_INSTANCE: fffffa8001fd9010 "FileInfo" "45000"
0: kd> !fltkd.instance fffffa800249cbb0
FLT_INSTANCE: fffffa800249cbb0 "FileInfo" "45000"
   FLT_OBJECT: fffffa800249cbb0  [01000000] Instance
      RundownRef               : 0x0000000000000000 (0)
      PointerCount             : 0x00000002
      PrimaryLink              : [fffffa800249c108-fffffa800249c108]
   OperationRundownRef      : fffffa800249b2f0
      Number                   : 2
      PoolToFree               : fffffa800249c910
      OperationsRefs           : fffffa800249c980  (0)
         PerProcessor Ref[0]      : 0xfffffffffffffe08 (-252)
         PerProcessor Ref[1]      : 0x00000000000001f8 (252)
   Flags                    : [00000000]
   Volume                   : fffffa800249c010 "\Device\Mup"
   Filter                   : fffffa80023009c0 "FileInfo"
   TrackCompletionNodes     : fffffa800249dfe0
   ContextLock              : (fffffa800249cc20)
   Context                  : fffffa80029186b0
   CallbackNodes            : (fffffa800249cc40)
   VolumeLink               : [fffffa800249c108-fffffa800249c108]
   FilterLink               : [fffffa80028fc6e0-fffffa8002300a80]

There really isn’t a lot here, most of the fields are related to keeping track of references. There are also the obvious pointers to the volume and filter objects for this instance.

Filter Manager Concepts: Part 3 – FLT_FILTER

Well, as you have probably guessed, FLT_FILTER is a structure that describes a minifilter. It is a pretty important structure and people usually become familiar with it once they discover that their minifilter is not unloading, which invariably happens at some point during development. In the grand scheme of things the FLT_FILTER is very similar to the DRIVER_OBJECT.

A thing worth mentioning is that a FLT_FILTER is linked into a specific frame based on its default altitude. This might trigger creation of a new frame (in fact loading a new filter is the only thing that can create a new frame).

Here is a picture of how FLT_FILTERs fit in the model:


You can see that a frame can contain one or more filters. It is possible that a frame has no filters at all if minifilters get unloaded since frames never go away (though i don’t show any empty frames in my picture).

Let’s look at one in the debugger:

1: kd> !fltkd.filter fffff9800696c6a0
FLT_FILTER: fffff9800696c6a0 "luafv" "135000"
   FLT_OBJECT: fffff9800696c6a0  [02000000] Filter
      RundownRef               : 0x0000000000000008 (4)
      PointerCount             : 0x00000001
      PrimaryLink              : [fffff980012e2b50-fffff9800122ab70]
   Frame                    : fffff9800122aac0 "Frame 0"
   Flags                    : [00000006] FilteringInitiated NameProvider
   DriverObject             : fffffa800407e080
   FilterLink               : [fffff980012e2b50-fffff9800122ab70]
   PreVolumeMount           : fffff880053a32d8  (no symbol)
   PostVolumeMount          : 0000000000000000  (null)
   FilterUnload             : 0000000000000000  (null)
   InstanceSetup            : fffff88001b5b010  (no symbol)
   InstanceQueryTeardown    : 0000000000000000  (null)
   InstanceTeardownStart    : 0000000000000000  (null)
   InstanceTeardownComplete : 0000000000000000  (null)
   ActiveOpens              : (fffff9800696c838)  mCount=1
   Communication Port List  : (fffff9800696c888)  mCount=0
   Client Port List         : (fffff9800696c8d8)  mCount=0
   VerifierExtension        : fffff98007c8ebc0
   Operations               : fffff9800696c9d0
   OldDriverUnload          : 0000000000000000  (null)
   SupportedContexts        : (fffff9800696c7c0)
      VolumeContexts           : (fffff9800696c7c0)
      InstanceContexts         : (fffff9800696c7c8)
         ALLOCATE_CONTEXT_NODE: fffff980068dcd80 "luafv" [01] LookasideList (size=856)
      FileContexts             : (fffff9800696c7d0)
      StreamContexts           : (fffff9800696c7d8)
      StreamHandleContexts     : (fffff9800696c7e0)
         ALLOCATE_CONTEXT_NODE: fffff980068dcec0 "luafv" [01] LookasideList (size=24)
      TransactionContext       : (fffff9800696c7e8)
   PagedContextNodeList     : (fffff9800696c930)  mCount=0
   NonPagedContextNodeList  : (fffff9800696c980)  mCount=1
   InstanceList             : (fffff9800696c6f8)
      FLT_INSTANCE: fffff98006ff44c0 "luafv" "135000"

As you can see there are lot of fields for callbacks and storing contexts and some lists. We’ll go into all those later, when we start exploring the !fltkd extension.