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.

Thursday, June 23, 2011

Rename in File System Filters - part II

In this second part about renames in file system filters I'll to cover what steps need to be taken if a filter wants to issue its own IRP_MJ_SET_INFORMATION request. Also I'll like to look at the FastFat WDK implementation and point out some interesting things.

So what does a minifilter need to do if it wants to issue its own IRP_MJ_SET_INFORMATION? It turns out that it's not really that much. FltMgr provides a FltSetInformationFile() API that can be used for this purpose. A quick peek at that function reveals that for rename operations it calls a function fltmgr!FltpOpenLinkOrRenameTarget which performs a similar role to IopOpenLinkOrRenameTarget. So it would seem that for a minifilter there is never a need to actually send an IRP_MJ_SET_INFORMATION request directly. However, I have run into a case where FltSetInformationFile failed with STATUS_SHARING_VIOLATION on a customer machine in a scenario I've never been able to reproduce. After some investigation I've discovered that fltmgr!FltpOpenLinkOrRenameTarget issues its own create with FILE_SHARE_READ | FILE_SHARE_WRITE and no FILE_SHARE_DELETE. I can't tell for sure why that's the case, but it's consistent with IopOpenLinkOrRenameTarget. However, in this particular case, in an interaction with the CSC driver (the client side caching component in windows) NtSetInformationFile() worked without my minifilter in the picture while my call to FltSetInformationFile failed. I tried hard to reproduce this problem but I couldn't make it happen on my local machine and since there was only one IRP_MJ_CREATE issued I decided that I should try to add SHARE_DELETE and see if it fixes the problem (and it did). So I needed to implement my own function and build a FLT_CALLBACK_DATA structure and then send it to the file system below. These are the necessary steps that mimic the steps that fltmgr!FltpOpenLinkOrRenameTarget takes (the function itself is quite long but if there is enough interest it'll post it as an example… leave me a private message):

  1. Allocate and initialize the FILE_RENAME_INFORMATION structure.
  2. Allocate and initialize a FLT_CALLBACK_DATA structure.
  3. Call FltCreateFileEx2 (or FltCreateFile depending on the OS version) to open a handle to the target directory. Make sure to use IO_OPEN_TARGET_DIRECTORY here.
  4. Compare the DEVICE_OBJECTs associated with the source and target FILE_OBJECTs (resolve the handle returned by FltCreateFile for that) and fail if they're not the same (STATUS_NOT_SAME_DEVICE).
  5. Set PFILE_RENAME_INFORMATION->RootDirectory to the handle I've just got.
  6. Set PFLT_CALLBACK_DATA->Iopb->Parameters.SetFileInformation.ParentOfTarget to the PFILE_OBJECT associated with handle in PFILE_RENAME_INFORMATION->RootDirectory.
  7. Call FltPerformSynchronousIo( PFLT_CALLBACK_DATA …)
  8. Cleanup (close the handles, dereference the FILE_OBJECTs, free any buffers and so on)...

Finally it's time to look at the FastFat WDK sample and see what IO_OPEN_TARGET_DIRECTORY does. I'm looking at the files under the \WinDDK\7600.16385.1\src\filesys\fastfat\Win7\ directory in case you want to follow along. The reason this is interesting is because when looking at FatSetRenameInfo it's easy to see that if TargetFileObject is present, the new name for the file is exactly the name for TargetFileObject, completely ignoring whatever is set in the actual FILE_RENAME_INFORMATION buffer (here is the interesting line):

            NewName = *((PUNICODE_STRING)&TargetFileObject->FileName);

So looking at the FatCommonCreate function to see what it does for SL_OPEN_TARGET_DIRECTORY it is obvious that FatOpenTargetDirectory is the function where the magic happens. What FatOpenTargetDirectory does is that it replaces the FILE_OBJECT->FileName with the final component of that path, which explains why in FatSetRenameInfo Fat can simply look at the TargetFileObject->FileName to get the file name. This is pretty interesting since (as I said before) it means that when the IRP_MJ_SET_INFORMATION IRP is processed by filters, any modifications to the FILE_RENAME_INFORMATION->FileName are ignored.

The next step was to see if the other file systems work in a similar fashion. Unfortunately the other file system that ships with the WDK is CDFS, which doesn't support renames (I guess that's because it was designed to work on CDs, which are read-only and so renames would make no sense). So I took the passthrough sample and modified it a bit so that it would break during a successful postCreate for an operation that had the SL_OPEN_TARGET_DIRECTORY flag set, so that I could investigate what happens with the file name. First let me post the source code for the minifilter (i've just modified PtPreOperationPassThrough and PtPostOperationPassThrough):

FLT_PREOP_CALLBACK_STATUS
PtPreOperationPassThrough (
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __deref_out_opt PVOID *CompletionContext
    )
...
{
    NTSTATUS status;

    UNREFERENCED_PARAMETER( FltObjects );
    UNREFERENCED_PARAMETER( CompletionContext );

    PT_DBG_PRINT( PTDBG_TRACE_ROUTINES,
                  ("PassThrough!PtPreOperationPassThrough: Entered\n") );

    if ((Data->Iopb->MajorFunction == IRP_MJ_CREATE) &&
        (FlagOn(Data->Iopb->OperationFlags, SL_OPEN_TARGET_DIRECTORY))) {

        //
        // this is an IRP_MJ_CREATE operation for a target of a rename.
        // tell the postCreate callback we'd like to break. Use the 
        // CompletionContext like a BOOLEAN variable.
        //

        *CompletionContext = (PVOID)TRUE;
    }
….


FLT_POSTOP_CALLBACK_STATUS
PtPostOperationPassThrough (
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __in_opt PVOID CompletionContext,
    __in FLT_POST_OPERATION_FLAGS Flags
    )
...
{
    UNREFERENCED_PARAMETER( Data );
    UNREFERENCED_PARAMETER( FltObjects );
    UNREFERENCED_PARAMETER( CompletionContext );
    UNREFERENCED_PARAMETER( Flags );

    PT_DBG_PRINT( PTDBG_TRACE_ROUTINES,
                  ("PassThrough!PtPostOperationPassThrough: Entered\n") );

    if ((CompletionContext != NULL) &&
        (Data->IoStatus.Status == STATUS_SUCCESS)) {

        DbgBreakPoint();
    }

    return FLT_POSTOP_FINISHED_PROCESSING;
}

Once I had the minifilter in place I fired up FileTest.exe and renamed a file to C:\rename_target_dir\rename_target_file.bin. My plan was that once it breaks in the debugger I would poke around and see what the FILE_OBJECT->FileName looks like. It turns out that NTFS follows a similar approach, except that the FILE_OBJECT->FileName for the directory that is opened (since when the SL_OPEN_TARGET_DIRECTORY flag is set the CREATE always opens a directory) points to the actual path for the directory. However, that path to the directory comes from the original rename target file path, which is just truncated to not include the file component. Then that final component hidden is used by the file system as the target of the rename in a similar fashion to FastFat. In order to make sure that NTFS actually uses the name in the FileObject I changed in the debugger so that the file name would be "rename_target_fi1e.bin".

1: kd> ?? FltObjects->FileObject
struct _FILE_OBJECT * 0x924a18e8
   +0x000 Type             : 0n5
   +0x002 Size             : 0n128
   +0x004 DeviceObject     : 0x92f0ebc8 _DEVICE_OBJECT
   +0x008 Vpb              : 0x92f0b210 _VPB
   +0x00c FsContext        : 0xa5962d08 Void
   +0x010 FsContext2       : 0xb127a610 Void
   +0x014 SectionObjectPointer : (null) 
   +0x018 PrivateCacheMap  : (null) 
   +0x01c FinalStatus      : 0n0
   +0x020 RelatedFileObject : (null) 
   +0x024 LockOperation    : 0 ''
   +0x025 DeletePending    : 0 ''
   +0x026 ReadAccess       : 0 ''
   +0x027 WriteAccess      : 0x1 ''
   +0x028 DeleteAccess     : 0 ''
   +0x029 SharedRead       : 0x1 ''
   +0x02a SharedWrite      : 0x1 ''
   +0x02b SharedDelete     : 0 ''
   +0x02c Flags            : 0
   +0x030 FileName         : _UNICODE_STRING "\rename_target_dir"
   +0x038 CurrentByteOffset : _LARGE_INTEGER 0x0
   +0x040 Waiters          : 0
   +0x044 Busy             : 0
   +0x048 LastLock         : (null) 
   +0x04c Lock             : _KEVENT
   +0x05c Event            : _KEVENT
   +0x06c CompletionContext : (null) 
   +0x070 IrpListLock      : 0
   +0x074 IrpList          : _LIST_ENTRY [ 0x924a195c - 0x924a195c ]
   +0x07c FileObjectExtension : (null) 
1: kd> ?? FltObjects->FileObject->FileName
struct _UNICODE_STRING
 "\rename_target_dir"
   +0x000 Length           : 0x24
   +0x002 MaximumLength    : 0x52
   +0x004 Buffer           : 0xb0796550  "\rename_target_dir"
1: kd> db 0xb0796550 L0x52
b0796550  5c 00 72 00 65 00 6e 00-61 00 6d 00 65 00 5f 00  \.r.e.n.a.m.e._.
b0796560  74 00 61 00 72 00 67 00-65 00 74 00 5f 00 64 00  t.a.r.g.e.t._.d.
b0796570  69 00 72 00 5c 00 72 00-65 00 6e 00 61 00 6d 00  i.r.\.r.e.n.a.m.
b0796580  65 00 5f 00 74 00 61 00-72 00 67 00 65 00 74 00  e._.t.a.r.g.e.t.
b0796590  5f 00 66 00 69 00 6c 00-65 00 2e 00 62 00 69 00  _.f.i.l.e...b.i.
b07965a0  6e 00                                            n.
1: kd> eb b0796596 0x31
1: kd> db 0xb0796550 L0x52
b0796550  5c 00 72 00 65 00 6e 00-61 00 6d 00 65 00 5f 00  \.r.e.n.a.m.e._.
b0796560  74 00 61 00 72 00 67 00-65 00 74 00 5f 00 64 00  t.a.r.g.e.t._.d.
b0796570  69 00 72 00 5c 00 72 00-65 00 6e 00 61 00 6d 00  i.r.\.r.e.n.a.m.
b0796580  65 00 5f 00 74 00 61 00-72 00 67 00 65 00 74 00  e._.t.a.r.g.e.t.
b0796590  5f 00 66 00 69 00 31 00-65 00 2e 00 62 00 69 00  _.f.i.1.e...b.i.
b07965a0  6e 00                                            n.
1: kd> g

After continuing execution the file on the file system was renamed to the new name that I had changed in the debugger and it thus validated my theory (well, almost.. It was still possible that the associated IRP_MJ_SET_INFORMATION was somehow initialized to use the buffer I've modified from the FileObject->FileName so I debugged and made sure that's not the case…)

Here are some more things that make renames difficult to deal with in a file system filter (in addition to the list at the end of last post):

  • A file system filter that needs to redirect a rename operation can't just rely on changing the destination name in the FILE_RENAME_INFORMATION buffer for renames where the FILE_RENAME_INFORMATION->RootDirectory is not null, since some file systems ignore that. Instead it needs to make sure that it creates a handle to the parent directory using the IO_OPEN_TARGET_DIRECTORY flag. However a filter must also change the FILE_RENAME_INFORMATION because another filter might rely on that (FltMgr's FltGetDestinationFileNameInformation for example) so data in the buffer and the data that the file system will use must be kept in sync.
  • There are issues with FltSetInformationFile where calling it for a FileRenameInformation will fail because of a sharing violation. If someone has run into this problem and has figured out why it happens or if they have some steps to reproduce it so that I could investigate it myself I'd appreciate if they contacted me offline .

Thursday, June 16, 2011

Rename in File System Filters - part I

Rename is one of the operations that a lot of different classes of minifilters need to handle. I have been surprised by how rename works on a couple of occasions and so I figured it might be worthwhile to talk about how it is implemented and what particular problems it creates for file system filters.

Before we go any further there are some architectural decisions that are important to understand in order to see where some of the peculiarities of the rename operation come from:

  • In general the file system on a volume isn't aware of the other volumes mounted by the same file system. This means that a mounted file system (which I will refer to as a file system volume to differentiate it from a storage volume) never performs operations on a different file system volume (for example the NTFS code running on C: never touches NTFS structures for D: and is generally unaware of the existance of D: altogether). This is a big architectural decision and it has the advantage that it can tremendously simplify implementation. It's easy to imagine how complicated the file system stack would become if a file system might need to take locks and references against a different file system volume for some operations. So because of this rename operations at the file system level only support the same file system volume, and in fact even the OS doesn't support cross-volume renames (actually this depends on what your definition for the OS is; there is no support at NT level (which to me is the OS) but Win32 however introduces some support for cross-volume renames).
  • File paths are a big part of what a file system does. In fact, maintaining the namespace is the most important function of a file system when considering the fact that file data storage is largely implemented by the storage stack and all the file system has to do is to translate file offsets into storage offsets (to be fair there are cases where the file system does more than that, for example when compressing or encrypting). So clearly a lot of the code in a file system is dedicated to maintaining and operating on the namespace. The one code path that obviously needs to deal with file names is the IRP_MJ_CREATE path (regardless of whether files and directories are opened or created). So naturally a lot of the coding effort goes into implementing and optimizing that path. Once a file is opened, the file system would prefer to not have to deal with the file name at all (and this is indeed how it's implemented). With this in mind it's easy to see that it is best if any operation that deals with names can reuse as much of the CREATE code path as possible. This reuse can take two forms, either by having the OS call IRP_MJ_CREATE every time it needs to pass a file path to the file system and getting back a FILE_OBJECT on which to operate, or by having the file system internally call a lot of the functions that collectively make up the file system create path (which doesn't really happen). This is why a lot of the Win32 APIs take file paths (like DeleteFile()) and then internally convert this into "handle = ZwCreteFile(file_path), internal_operation(handle), ZwClose(handle)".

First let's look at what the APIs are that are usually used to rename a file. The usermode APIs are:

BOOL WINAPI MoveFile(
  __in  LPCTSTR lpExistingFileName,
  __in  LPCTSTR lpNewFileName
);

BOOL WINAPI MoveFileEx(
  __in      LPCTSTR lpExistingFileName,
  __in_opt  LPCTSTR lpNewFileName,
  __in      DWORD dwFlags
);

BOOL WINAPI MoveFileWithProgress(
  __in      LPCTSTR lpExistingFileName,
  __in_opt  LPCTSTR lpNewFileName,
  __in_opt  LPPROGRESS_ROUTINE lpProgressRoutine,
  __in_opt  LPVOID lpData,
  __in      DWORD dwFlags
);

BOOL WINAPI MoveFileTransacted(
  __in      LPCTSTR lpExistingFileName,
  __in_opt  LPCTSTR lpNewFileName,
  __in_opt  LPPROGRESS_ROUTINE lpProgressRoutine,
  __in_opt  LPVOID lpData,
  __in      DWORD dwFlags,
  __in      HANDLE hTransaction
);

These APIs follow a sort of progression, adding more and more options. However, for a file system developer it doesn't really matter much whether the user wants to track the progress of the operation or whether they specify a transaction or not (since at the file system filter's level the transaction is always available if the OS supports transactions). The most important parameters are the existing file name, the destination file name and the flags. Of the flags the most important one is MOVEFILE_COPY_ALLOWED because it instructs the IO manager that if the destination file is on different volume it should copy the file to the new volume and delete it from the source volume. This flag is usually specified and it's on by default when calling MoveFile (on my Win7 VM MoveFileW() is just a wrapper over MoveFileWithProgressW() where this is the only flag specified).

Now, when it gets to the OS layer, the way to issue a rename is by calling ZwSetInformationFile() with the FileRenameInformation information class, which requires the FILE_RENAME_INFORMATION:

NTSTATUS ZwSetInformationFile(
  __in   HANDLE FileHandle,
  __out  PIO_STATUS_BLOCK IoStatusBlock,
  __in   PVOID FileInformation,
  __in   ULONG Length,
  __in   FILE_INFORMATION_CLASS FileInformationClass
);

typedef struct _FILE_RENAME_INFORMATION {
  BOOLEAN ReplaceIfExists;
  HANDLE  RootDirectory;
  ULONG   FileNameLength;
  WCHAR   FileName[1];
} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;

The documentation for FILE_RENAME_INFORMATION and for IRP_MJ_SET_INFORMATION is pretty good and explains a lot about the parameters. However, the documentation for ZwSetInformationFile does not mention the FileRenameInformation case, which is actually pretty interesting.

One thing I mentioned at the beginning of this post is that one file system volume doesn't ever interact with another file system volume. Since the target of a rename can be a full path and since it can point to a different volume, the OS should only send the path down if it is on the same volume. So what happens is that ZwSetInformationFile must validate if the rename is within the same volume before sending the IRP_MJ_SET_INFORMATION request down. This is achieved in the function IopOpenLinkOrRenameTarget, which performs a couple of simple steps:

  1. check if the FILE_RENAME_INFORMATION->RootDirectory is a user mode handle and if so convert it to a kernel handle by calling IoConvertFileHandleToKernelHandle. This is very important for filter writers because it means that a filter cannot expect that the handle is kernel handle.
  2. issue an IoCreateFileEx to open the target of the rename, which is either a full rename or a relative one (depending on whether FILE_RENAME_INFORMATION->RootDirectory is NULL or not). This IoCreateFileEx inherits both the transaction and the DeviceObject hint from the source file object. Also this IRP_MJ_CREATE always has the SL_OPEN_TARGET_DIRECTORY flag set.
  3. If the create succeeds then the DeviceObject for the source FILE_OBJECT is compared with the DeviceObject for the target FILE_OBJECT and if they are different then IopOpenLinkOrRenameTarget returns STATUS_NOT_SAME_DEVICE.
  4. If they are the same then the new FILE_OBJECT (which is always a directory, the parent directory of the path specified by FILE_RENAME_INFORMATION->FileName and is always obtained from the IRP_MJ_CREATE with the SL_OPEN_TARGET_DIRECTORY as explained above) is set into IrpSp->Parameters.SetFile.FileObject. This FILE_OBJECT is the used by the file system to determine the parent directory for the target of the rename in the file system.

Once IopOpenLinkOrRenameTarget returns the only thing that remains for ZwSetInformationFile to do is to send the IRP to the file system.

I have some more things to say about renames that I'll save for next week, but I'd like to close with a list of things that make life really hard for file system developers:

  • The fact that ZwSetInformationFile performs the check of whether a certain rename would be a cross-volume rename before issuing the actual IRP_MJ_SET_INFORMATION means that all a file system filter sees is an IRP_MJ_CREATE file with SL_OPEN_TARGET_DIRECTORY, but it cannot know what the source file for this rename is. This is problematic for filters that might need to redirect renames to different locations depending on the source file (an application virtualization filter for example might want to redirect renames for certain files to some other location); such filters need to actually wait until the IRP_MJ_SET_INFORMATION request arrives and change the destination at that point. Another class of filters that is impacted by this are filters that do something where they create a virtual namespace in a volume (for example in a directory on a volume) by bringing contents from another volume. Clearly renames won't work across volumes, but even if the filter is prepared to implement something similar to the MOVEFILE_COPY_ALLOWED flag and copy the file if it is across volumes, it doesn't have the option because an IRP_MJ_SET_INFORMATION will simply not arrive if the target and the source are on different volumes. One feature that I feel would be very helpful here would be to add the source FILE_OBJECT into an ECP on the IRP_MJ_CREATE issued by IopOpenLinkOrRenameTarget, which would allow filters to detect what is the source file for the rename.
  • Another thing that makes things unnecessarily complicated for filter developers is that reusing of the DeviceObject hint in IopOpenLinkOrRenameTarget. This means that anytime a minifilter wants to rename a FILE_OBJECT that it created via FltCreateFile it must use as the FILE_RENAME_INFORMATION->FileName parameter a path on the same device as the FILE_OBJECT that it has created. This isn't that big of a problem since the minifilter must already know the right device, but it might still need to do some file path manipulation to make sure that the path itself is on that device (which can happen if the target of a rename comes from the user and it contains a reparse point). Just something to keep in mind I guess.
  • The presence of a handle (which is possibly a user mode handle) in the FILE_RENAME_INFORMATION structure means that a minifilter that wants to pend a rename operation must take extra steps to make sure that the handle is still valid in the context of the process where the thread handling the pended rename runs. It must keep all the data in the FILE_RENAME_INFORMATION and the IrpSp->Parameters.SetFile in sync because it can't make any assumption which data point another file system filter or even the file system might use.
  • The fact that the FILE_OBJECT that was opened by IopOpenLinkOrRenameTarget is used by the file system when processing the rename and in particular how it is used also means that filters that want to issue their own rename operation (by directly issuing an IRP or FLT_CALLBACK_DATA) must duplicate the stepts the OS takes in order to build a proper request. But more on this next week.

Thursday, June 9, 2011

Handling IRP_MJ_NETWORK_QUERY_OPEN in a Minifilter

There are a couple of things that are worth mentioning about IRP_MJ_NETWORK_QUERY_OPEN from a minifilter writer perspective. The first interesting thing is that IRP_MJ_NETWORK_QUERY_OPEN is not the minifilter virtual IRP for FastIoQueryNetworkOpenInfo as the name might suggest. The way FltMgr dispatches FastIoQueryNetworkOpenInfo is by sending an IRP_MJ_QUERY_INFORMATION FLT_CALLBACK_DATA structure with the FLTFL_CALLBACK_DATA_FAST_IO_OPERATION flag set. IRP_MJ_NETWORK_QUERY_OPEN is in fact the operation for FastIoQueryOpen (which is a FastIo operation that doesn't really translate into any of the IRP type operations so that's why the IRP_MJ_NETWORK_QUERY_OPEN virtual IRP was added).

So in order to discuss the interesting aspects of IRP_MJ_NETWORK_QUERY_OPEN we need to discuss when FastIoQueryOpen is called and why. The main reason for this FastIo is that it wraps a set of operations (IRP_MJ_CREATE, IRP_MJ_QUERY_INFORMATION(FILE_NETWORK_OPEN_INFORMATION), IRP_MJ_CLEANUP,IRP_MJ_CLOSE) into one call, allowing the caller to get information about a file or just to check whether the file exists directly by name, without actually opening the file (see http://blogs.msdn.com/b/oldnewthing/archive/2007/10/23/5612082.aspx). This might not look like much but when dealing with a network protocol being able to do multiple things in one request can improve performance a lot. So it seems that this particular FastIo was introduced to really optimize this one thing, getting the FILE_NETWORK_OPEN_INFORMATION for a file by name.

In kernel mode one way to generate this FastIo (but not the only way) is by calling IoFastQueryNetworkAttributes(). This function is not documented in MSDN but it's pretty straightforward to figure out (though minifilters should not call this API since it breaks layering… It might be useful for testing IRP_MJ_NETWORK_QUERY_OPEN so that's way I'm mentioning it here). The way it works is by eventually calling IopParseDevice with an OPEN_PACKET that has the QueryOnly flag set. What happens in this case is that IopParseDevice allocates an IRP_MJ_CREATE IRP and passes it as a parameter to the FastIoQueryOpen callback. If the call is successful then IopParseDevice returns, if it is not then the already allocated IRP_MJ_CREATE IRP is sent down the stack the usual way.

One thing that is very interesting is the way the callbacks are defined:

typedef union _FLT_PARAMETERS {
  ... ;
  struct {
    PIRP                           Irp;
    PFILE_NETWORK_OPEN_INFORMATION NetworkInformation;
  } NetworkQueryOpen;
  ... ;
} FLT_PARAMETERS, *PFLT_PARAMETERS;

This snippet is taken from the MSDN page: FLT_PARAMETERS for IRP_MJ_NETWORK_QUERY_OPEN Union. There are quite a few things mentioned on that page that are interesting:

  • First and foremost there is the IRP parameter. This is interesting because it's the only place in a minifilter where one gets to see an IRP. I remember hearing that there was some debate in the FltMgr team whether this IRP should be wrapped in a FLT_CALLBACK_DATA (or maybe just an FLT_IO_PARAMETER_BLOCK), but it wouldn't be pretty no matter what and having an IRP here doesn't really change anything as it should be treated just like a structure (as opposed to a regular IRP on which methods like IoCallDriver can be called).
  • Then there is the note that "The file object associated with IRP_MJ_NETWORK_QUERY_OPEN is a stack-based object.A filter registered for the NetworkQueryOpen callback must not reference this object. That is, do not call ObReferenceObject or ObDereferenceObject on this stack-based file object. Also, do not save a pointer to the object.". The main idea here is that this particular operation was designed to be fast and obviously just allocating the FILE_OBJECT structure on the stack and then initializing some members in it is faster than allocating a full FILE_OBJECT. Also, the FILE_OBJECT is not going to be used anywhere, it is simply a way to let the file system know the path to the file for which the FILE_NETWORK_OPEN_INFORMATION is required. If the FastIo doesn’t work then when the full IRP_MJ_CREATE is sent down a full FILE_OBJECT is allocated.
  • Also there is a mention that "A filter must register for this operation". This is not true. A minifilter can safely ignore this operation.

So let's talk about some of the things minifilters developers might need to be aware of in this case:

  • When the IRP_MJ_NETWORK_QUERY_OPEN callback is called, the FILE_OBJECT is not opened and the minifilter is technically in a preCreate state. So trying to use any FILE_OBJECT related context (like FileContext, StreamContext and StreamHandleContext) will not work.
  • Moreover, the FILE_OBJECT is not a real FILE_OBJECT so there are some things that don't really apply to it (maybe not all the flags or all the members are set like they would on a real FILE_OBJECT - I didn't actually ever check; at least reference counting seems to not work as the note indicates).
  • When completing IRP_MJ_NETWORK_QUERY_OPEN with something other than STATUS_FLT_DISALLOW_FAST_IO (such as when the minifilter actually completes the operation successfully), the minifilter must set the status into IRP->IoStatus and not into the FLT_CALLBACK_DATA. The FLT_CALLBACK_DATA represents the current operation and the status is the status for the FastIo and not the status for the IRP. For example a minifilter that wants to complete IRP_MJ_NETWORK_QUERY_OPEN call to indicate that the file doesn't exist should set Irp->IoStatus.Status to the appropriate status (like STATUS_OBJECT_NAME_NOT_FOUND or STATUS_OBJECT_PATH_NOT_FOUND or whatever) and the CallbackData->IoStatus.Status to STATUS_SUCCESS to indicate that the FastIo has completed successfully.
  • Finally there are some layering issues with IRP_MJ_NETWORK_QUERY_OPEN which make it possible for minifilters to have their callback invoked for operations that should be layered below them. These are rather rare cases but they can happen and are painful to fix and to debug.

So as you can see properly handling IRP_MJ_NETWORK_QUERY_OPEN is a rather complicated deal. Moreover, the LUAFV minifilter that ships with Windows always returns STATUS_FLT_DISALLOW_FAST_IO for it, effectively failing the request and forcing it down the regular IRP_MJ_CREATE path. My recommendation is that unless a minifilter runs in an environment where LUAFV is not present (some server builds maybe) and the minifilter does very minimal processing in the handler for IRP_MJ_NETWORK_QUERY_OPEN (because if it does any heavy lifting like calling FltGetFileNameInformation() or something more complicated then it's not really "Fast"Io anymore anyway and there is no performance benefit in supporting this code path) then the minifilter is better off blindly returning STATUS_FLT_DISALLOW_FAST_IO and dealing with request when it comes down the IRP_MJ_CREATE path.

Thursday, June 2, 2011

Verifier Checks: A filter has completed an operation but changed the TargetFileObject or the TargetInstance

So I'm back from my vacation and I can tell it was a good one since I don't remember anything about file system filters or driver development in general :). I'll have to re-read this blog and maybe things will start coming back to me… Anyway, joking aside, I need to make this a pretty short post since I'm way behind on my emails and so I'm just going to talk about a verifier check that is new to Win7. The message is:

FILTER VERIFIER ERROR: A filter has completed an operation but changed the TargetFileObject or the TargetInstance. This change will be ignored and nobody above will see this. Please make sure the design doesn't rely on this working!(Filter = Xxx, Cbd = Xxx)

The TargetFileObject and TargetInstance are members of the FLT_IO_PARAMETER_BLOCK structure, usually found inside a FLT_CALLBACK_DATA structure. This is how they are usually used in a minifilter:

  • the TargetFileObject member can be changed by a minifilter to target the operation at a different FILE_OBJECT for all filters below (and of course for the filesystem). This is quite useful in some scenarios, like SFO type filters and filters that want to redirect some but not all operations to a different FILE_OBJECT.
  • the TargetInstance member is used by a minifilter when it wants to send the request to a different volume (it cannot be used to send the request from InstanceA to InstanceB on the same volume since that would bypass all filters with altitudes between InstanceA and InstanceB which is illegal). So TargetInstance must be set to an instance at the same altitude (therefore on a different volume). The net effect of this is that all filters below the filter on the first volume will not see the IO and it will be shown instead to filters below the new instance on the new volume.

FltMgr must be notified about any change to these two members through a call to FltSetCallbackDataDirty(). If FltSetCallbackDataDirty() is not called, FltMgr might ignore the changes in some cases and the behavior is undefined.

So now that we've explained why anyone would want to change these values, let's talk about the Verifier Check. The check simply means that FltMgr detected that a minifilter changed one of these members but then completed the operation. The effect of this is that the changes will be ignored, because they only impact filters below the minifilter and since the operation has been completed by the minifilter no filters below it will ever see it so there is no point to changing these members in the first place anyway. FltMgr reports this error because it might lead on referencing issues (the minifilter may have added references to the new FileObject or Instance) or potentially erroneous runtime filter behavior.

In general there are two possible reasons for seeing this error:

  • There is a bug in the filter where it doesn't mean to complete this operation after it changed the TargetFileObject or TargetInstance, so the solution in this case is to fix the code and not complete the operation and just send it down.
  • The operation was supposed to be completed but there is common code executed before the operation is completed where TargetFileObject or TargetInstance are changed. The fix in this case if obviously to change to code so that it the operation will be completed then the code that changes TargetFileObject or TargetInstance is not executed.