Thursday, June 30, 2011

Using FltGetFileNameInformationUnsafe

I was talking to someone recently and I realized that the FltGetFileNameInformationUnsafe() is an API is that virtually unknown and, as a result, unused. This post is meant to explain where FltGetFileNameInformationUnsafe() fits in the overall set of FltMgr name APIs and when and how it should be used. But first let's see what it looks like.

__checkReturn
__drv_maxIRQL(APC_LEVEL) 
NTSTATUS
FLTAPI
FltGetFileNameInformationUnsafe (
    __in PFILE_OBJECT FileObject,
    __in_opt PFLT_INSTANCE Instance,
    __in FLT_FILE_NAME_OPTIONS NameOptions,
    __deref_out PFLT_FILE_NAME_INFORMATION *FileNameInformation
    );
We'll compare it with FltGetFileNameInformation() :

__checkReturn
__drv_maxIRQL(APC_LEVEL) 
NTSTATUS
FLTAPI
FltGetFileNameInformation (
    __in PFLT_CALLBACK_DATA CallbackData,
    __in FLT_FILE_NAME_OPTIONS NameOptions,
    __deref_out PFLT_FILE_NAME_INFORMATION *FileNameInformation
    );
So as you can see there are a couple of differences:

  • FltGetFileNameInformation() takes a FLT_CALLBACK_DATA structure. This makes it impossible to be called when a minifilter might want to get the name of a file outside the context of an IO operation. Consider for example an activity monitor filter. In order to have as little impact on the performance of the system the minifilter should to record some information about the operation as quickly as possible. Such a minifilter might implement a scheme where it references the FILE_OBJECT on which an operation happens and then resolve the FILE_OBJECT to a file name only later, in a different logging thread, outside of the context of the IO operation. Which means that the minifilter might want to call FltGetFileNameInformation and not have a FLT_CALLBACK_DATA structure.
  • FltGetFileNameInformationUnsafe() takes a FILE_OBJECT and a FLT_INSTANCE parameter. However, the FILE_OBJECT alone should be enough for FltMgr to return a name (the way IoQueryFileDosDeviceName() can get the name) so where does the FLT_INSTANCE come in ? As I've said in other posts, the name of the file might change at different points in the file system stack. If a minifilter virtualizes the namespace then it's possible that the name of a file as seen above that filter is different from the name as seen below the filter. As such, the altitude for the name is important and the FLT_INSTANCE is used to figure out for which altitude in the file system stack should the file name be returned.
  • FLT_INSTANCE is optional. The MSDN page for FltGetFileNameInformationUnsafe states that FLT_INSTANCE is optional to allow for the case when a minifilter doesn't yet have an instance, such as in DriverEntry. However, I'd be curious to see such a case, since I can't imagine how a filter would get a FILE_OBJECT in DriverEntry without having opened the file in the first place…
  • There is no FLT_FILE_NAME_QUERY_ALWAYS_ALLOW_CACHE_LOOKUP flag for FltGetFileNameInformationUnsafe(). The reason for this is that since FltGetFileNameInformationUnsafe() doesn't perform the checks that FltGetFileNameInformation() uses to know if it's safe to call into the file system this flag doesn't make sense.
When I set a breakpoint on FltGetFileNameInformationUnsafe in a win7 VM and then tried to start IE, it immediately triggered. The stack looked like this:
1: kd> kb
ChildEBP RetAddr  Args to Child              
99c714a4 96492bd4 941ae9a0 00000000 00000101 fltmgr!FltGetFileNameInformationUnsafe
99c714c4 96492c65 941ae9a0 99c714e4 99c714e0 tcpip!WfpAleQueryNormalizedImageFileName+0x26
99c714e8 96492e85 941ae9a0 99c71528 99c71538 tcpip!WfpAleCaptureImageFileName+0x21
99c7153c 82a8f2c6 9235b578 000001f8 99c71560 tcpip!WfpCreateProcessNotifyRoutine+0xe3
99c715f4 82a8e5af 92367948 0135b578 99c71650 nt!PspInsertThread+0x5c0
99c71d00 8287b44a 03cce628 03cce604 02000000 nt!NtCreateUserProcess+0x742
99c71d00 774764f4 03cce628 03cce604 02000000 nt!KiFastCallEntry+0x12a
WARNING: Stack unwind information not available. Following frames may be wrong.
03cce948 76592059 00000000 03f26e74 0246da18 ntdll!KiFastSystemCallRet
03cce980 759051e6 03f26e74 0246da18 00000000 kernel32!CreateProcessW+0x2c
03ccea78 75912c74 0002015e 00000000 03f26e74 SHELL32!_SHCreateProcess+0x251
03cceacc 75904fc5 00000001 03f250e8 00000001 SHELL32!CExecuteApplication::_CreateProcess+0xfc
The interesting to note here is that tcpip directly calls this API. As you can imagine tcpip doesn't really have an instance (not being a minifilter) and so I was wondering what FLT_INSTANCE it might be using, if any. Well, as you probably expect, it's not really using an instance, it's just passing in NULL. So this is an example of a regular driver (not a file system filter) calling FltGetFileNameInformationUnsafe to get the name of a file. This is actually a pretty neat idea since FltGetFileNameInformationUnsafe uses FltMgr's name cache and so it must perform better than querying the name from the file system every time. Also, this allows a caller to request a normalized name, as opposed to IoQueryFileDosDeviceName() which just gets the FileNameInformation information class directly from a file object. However, the implication is that name provider callbacks in a minifilter will impact more than just other file system filters, because name providers callbacks are used by FltGetFileNameInformationUnsafe() which is in turn used by the OS.

In my opinion, the "Unsafe" part of the name refers to two things. It's not safe to call this in some cases listed in the MSDN page for the API, because doing so might deadlock. FltGetFileNameInformation() actually checks for these cases and won't call into the file system if it's not safe to do so. Another way this API is unsafe is in that it should never be called from a minifilter while processing an operation (either during a preOp or postOp callback). FltGetFileNameInformation is the API to call in all those cases.
To wrap up, these are cases where this function can be called:

  • In a minifilter, but outside the context of an IO operation. For example, in a worker thread or something similar. Also, it can be used if the minifilter requires the name of a FILE_OBJECT that belongs to a different volume, but I have a hard time coming up with such a scenario.
  • In a legacy file system filter, outside the context of an IO operation. For example, in a worker thread or something similar.
  • In a minifilter in the PFLT_GENERATE_FILE_NAME callback, where the CallbackData parameter is null. The CallbackData parameter can only be null if this request originates from a FltGetFileNameInformationUnsafe as well.
  • In a regular driver (not a file system filter) where the driver has a FILE_OBJECT and it needs a normalized name. This is not documented by MS as being supported (in fact, they're being pretty specific about this case) so the supported route would be to call IoQueryFileDosDeviceName().The only advantages of calling FltGetFileNameInformationUnsafe() instead of IoQueryFileDosDeviceName() are the fact that FltGetFileNameInformationUnsafe() can use FltMgr's cache and that it can return a normalized name. Also, another edge case might be that IoQueryFileDosDeviceName() is documented as only being available since XP, while FltMgr was available on Win2K and as such FltGetFileNameInformationUnsafe() might be available there as well, though I've not tried it...
And these are cases where it should NOT be called:
  • In a minifilter in the context of an IO operation.
  • In a legacy file system filter in the context of an IO operation.
  • In any of the cases documented on the MSDN page for the function.