Thursday, February 11, 2010

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.

9 comments:

  1. Im experiencing a weird behaviour related to mini filter and reparses - maybe an expert like you would know why. A call to FltGetFileInformation fails with error STATUS_INVALID_DEVICE_OBJECT_PARAMETER when called from PostCreate.

    The file for which it fails is a "reparse destination", meaning that another filter below mine returned STATUS_REPASE, and then the OS opened it again on another device. The failure is in the 2nd create (on the destination device).

    The volume for which this happens is the reparse target \Device\TargetDevice but the FileName field in the file object has the source device:
    _UNICODE_STRING "\Device\HarddiskVolume1\Documents and Settings\Administrator\Desktop\test.txt"

    How can i obtain the file name from my filter in this case?

    ReplyDelete
  2. Alex, something related to Verifier checks:
    If we try to issue a Paging I/O write with a buffer allocated using FltAllocateBufferAlignedWithTag, a sector aligned offset BUT unaligned length (this is because it is the last page of the file), the verifier complains that the minifilter has tried to issue a write with unaligned buffer.
    Isn't Paging writes an exception to this rule?

    ReplyDelete
  3. Two questions related to minifilters that need to complete the Create IRP:
    1. What is the minimal work that needs to be done while completing the Create IRP?
    2. Doen't each layer of name normalization cause performance impact due to the additional IRP_MJ_CREATE and IRP_MJ_DIRECTORY_CONTROL sent for the parent directory? What kind of impact has generally been observed by you?

    ReplyDelete
  4. Hi Nuke,

    Sorry for the delay.. Have you found a way around this ?

    I'm not sure I understand your scenario exactly. The picture i have is this: you have volume A and volume B, with your instance A and instance B. You see an IRP_MJ_CREATE on instance A and it is completed with STATUS_REPARSE by someone below. Then you see it on instance B and in postCreate you call FltGetFileNameInformation which fails ? Or is this in the postCreate in instance A where you've just received the STATUS_REPARSE ?

    ReplyDelete
  5. Hi Ayush!

    Well, minimal work for successfully completing an IRP_MJ_CREATE (failing a create doesn't require much additional work) really depends on what your minifilter does. Anyway, the main rule is that your FILE_OBJECT should NEVER reach the file system below (or any other file system for that matter). So in short, a minifilter that completes a create with STATUS_SUCCESS must filter all filtering points on the system (register for all possible FltMgr callbacks, including name provider callbacks and such).

    Name normalization is very performance heavy, though as far as i know there is a lot of work in the file system team to improve performance in most scenarios. But it is, indeed, pretty bad (hence the need for a name cache). In IRP_MJ_DIRECTORY_CONTROL it really depends on what you do in your minifilter...

    About that verifier check, i'm not sure what's going on there but my (very foggy) recollection was that you would still need to send the buffer aligned and the file system would internally take care of the stuff past the end of the file. Have found a better answer ? Please post if you did...

    ReplyDelete
  6. Regarding FLT_FILE_NAME_INFORMATION, perhaps I'm misunderstanding, but on the MSDN page for that struct, it says that you cannot modify the contents of the structure, but there is an implication in your post that you can if the filter driver is a name provider?

    The scenario I'm thinking about is this. Is it possible to look for a given path that is about to be written to and change it, forcing the write to occur at a different location from the original?

    ReplyDelete
  7. Hi DAE,

    No, you still can't change the FLT_FILE_NAME_INFORMATION structure. The name provider APIs have a special structure that they can change, the FLT_NAME_CONTROL. See the documentation for PFLT_GENERATE_FILE_NAME.

    I don't quite understand the scenario. Look for a given path where ? In the name provider callback ? Where is that path about to be written ? Are you trying to redirect IO from file "foo.txt" to "bar.txt" ?

    ReplyDelete
  8. Thanks for the reply! The last scenario is essentially it, but instead of changing the filename, I want to change where the file is written. For example, you're in Wordpad and save a file to C:\DAEFolder\Foo.txt (where you have no write access), but it actually gets written to D:\DAEFolder\Foo.txt (where you do have write access). I was looking at the KB article (http://support.microsoft.com/kb/319447) which discusses reparsing and on the surface sounds like a similar situation, but so far is only resulting in a lot of VM bluescreens.

    ReplyDelete
  9. In that case probably the easiest way to go is to reparse. There is actually a sample in the WDK, SimRep. Contact me offline if you have more questions about that.

    ReplyDelete