Thursday, February 23, 2012

IFS Plugfest

Hi everyone. Sorry, I've been meaning to post this earlier but I didn't get a chance. I'm currently at the IFS Plugfest event so there will be no post this week. I'll also be away next week for the MVP Summit event and so I don't expect I'll get a chance to post anything next week either.

Thursday, February 16, 2012

Reparsing to a Different Volume in Win7 and Win8

So last time I mentioned what I believe to be one of the most serious problems that STATUS_REPARSE introduces, namely the fact that FltCreateFile above a STATUS_REPARSE that reparses to a different volume fails with STATUS_MOUNT_POINT_NOT_RESOLVED. In this post I’d like to illustrate how to write a filter that can expose this problem as well as to show some steps that Microsoft seems to have take in Win8 to address this issue. The code also shows how to attach an ECP to an IRP_MJ_CREATE request issued by a filter.

So first let me describe my setup. There are two volumes, C: and E:. I have created the file E:\test.txt and then using mklink I have created a symlink from C:\myfile.txt to E:\test.txt. I have a minifilter based on the passthrough sample that whenever it intercepts a create for any file named "myfile.txt" (regardless of the path) tries to issue a FltCreateFileEx2(). I've added a DbgBreakPoint() right before the FltCreateFileEx2() call so that we can see the status and inspect the variables. The environments are Win7 (latest SP) and Win8 developer preview.

So this is the code I've used to illustrate the problem (code I've added to the passthrough sample):

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


    if (Data->Iopb->MajorFunction == IRP_MJ_CREATE) {

        UNICODE_STRING myFile = RTL_CONSTANT_STRING( L"myfile.txt" );
        OBJECT_ATTRIBUTES fileAttributes;
        HANDLE fileHandle = NULL;
        IO_STATUS_BLOCK ioStatus;
        IO_DRIVER_CREATE_CONTEXT driverContext;

        __try {

            //  
            // this is a preCreate, get the name.  
            //  
            status = FltGetFileNameInformation( Data,   
                                                FLT_FILE_NAME_OPENED | FLT_FILE_NAME_QUERY_DEFAULT,   
                                                &fileName );  

            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            // we need to parse the name to get the final component
            //

            status = FltParseFileNameInformation( fileName );

            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            //  Compare to see if this is a file we care about.
            //

            if (!RtlEqualUnicodeString( &fileName->FinalComponent,
                                        &myFile,
                                        TRUE )) {

                __leave;
            }

            //
            // initialize the driver context and the attributes. 
            //

            IoInitializeDriverCreateContext( &driverContext );

            InitializeObjectAttributes( &fileAttributes, 
                                        &fileName->Name,
                                        OBJ_KERNEL_HANDLE,
                                        NULL,
                                        NULL );

            //
            // finally issue the create.
            //

            status = FltCreateFileEx2( FltObjects->Filter,
                                       FltObjects->Instance,
                                       &fileHandle,
                                       NULL,
                                       FILE_READ_DATA | SYNCHRONIZE,
                                       &fileAttributes,
                                       &ioStatus,
                                       NULL,
                                       FILE_ATTRIBUTE_NORMAL,
                                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                       FILE_OPEN,
                                       FILE_SYNCHRONOUS_IO_ALERT,
                                       NULL,
                                       0,
                                       0,
                                       &driverContext ); 

            //
            // check the status.
            //

            DbgBreakPoint();

        } __finally {

            if (fileName != NULL) {

                FltReleaseFileNameInformation( fileName );
            }

            if (fileHandle != NULL) {

                ZwClose(fileHandle);
            }
        }
    }


    //
    //  See if this is an operation we would like the operation status
    //  for.  If so request it.

So as you can see we're not doing much here. As mentioned before, when trying to open c:\myfile.txt we will hit the breakpoint and the status will be STATUS_MOUNT_POINT_NOT_RESOLVED.

Looking in the win8 ddk we can see some interesting definitions for an ECP with the GUID GUID_ECP_FLT_CREATEFILE_TARGET and for a structure with the name FLT_CREATEFILE_TARGET_ECP_CONTEXT. The comment states that "This GUID and structure define an extra create parameter used for passing back reparse information from FltCreateFileEx2 when the create is reparsed to a different device.". When an ECP is used to pass back information it means that the caller of the IRP_MJ_CREATE must set an ECP list into the DriverContext otherwise any ECP that is added to the IRP_MJ_CREATE will be discarded before IoCreateFileEx returns and so there is no way to get that ECP back. First thing I tried was to set an ECP list and see if the IO manager or the FS allocates an ECP with the information and passes it back to me, but I didn't get any. Next thing I tried was to allocate an EPC and add it to the ECP list and that seemed to do the trick, the ECP that I allocated was filled with data. But before we look at the debugger data, let me paste the code that I used for Win8. BTW, all I did was to copy the definitions from the header into the passThrough.c file and compiled with the Win7 WDK. This works because everything except the structure definition and the ECP GUID was available in Win7 anyway. So here is the code:
    PT_DBG_PRINT( PTDBG_TRACE_ROUTINES,  
           ("PassThrough!PtPreOperationPassThrough: Entered\n") );  


    if (Data->Iopb->MajorFunction == IRP_MJ_CREATE) {

        UNICODE_STRING myFile = RTL_CONSTANT_STRING( L"myfile.txt" );
        OBJECT_ATTRIBUTES fileAttributes;
        HANDLE fileHandle = NULL;
        IO_STATUS_BLOCK ioStatus;
        IO_DRIVER_CREATE_CONTEXT driverContext;
        PECP_LIST ecpList = NULL;
        PFLT_CREATEFILE_TARGET_ECP_CONTEXT targetEcp = NULL;

        NTSTATUS ecpFindStatus;
        ULONG targetEcpSize = 0;


        __try {

            //  
            // this is a preCreate, get the name.  
            //  

            status = FltGetFileNameInformation( Data,   
                                                FLT_FILE_NAME_OPENED | FLT_FILE_NAME_QUERY_DEFAULT,   
                                                &fileName );  
            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            // we need to parse the name to get the final component
            //

            status = FltParseFileNameInformation( fileName );

            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            //  Compare to see if this is a file we care about.
            //

            if (!RtlEqualUnicodeString( &fileName->FinalComponent,
                                        &myFile,
                                        TRUE )) {

                __leave;
            }

            //
            // initialize the driver context.
            //

            IoInitializeDriverCreateContext( &driverContext );

            //
            // allocate the ECP list we're going to add to our create
            // 

            status = FltAllocateExtraCreateParameterList( FltObjects->Filter,
                                                         0,
                                                         &ecpList );

            if (!NT_SUCCESS(status)) {
                
                DbgBreakPoint();
                __leave;
            }

            //
            // allocate an ECP
            //

            status = FltAllocateExtraCreateParameter( FltObjects->Filter,
                                                      &GUID_ECP_FLT_CREATEFILE_TARGET,
                                                      sizeof(FLT_CREATEFILE_TARGET_ECP_CONTEXT),
                                                      0,
                                                      MyECPCleanupCallback,
                                                      'pcET',
                                                      &targetEcp );

            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            // don't initialize anything, just zero out the ECP and add it to the ECP list.
            //

            RtlZeroMemory( targetEcp, sizeof(FLT_CREATEFILE_TARGET_ECP_CONTEXT));

            status = FltInsertExtraCreateParameter( FltObjects->Filter,
                                                    ecpList,
                                                    targetEcp );

            if (!NT_SUCCESS(status)) {

                DbgBreakPoint();
                __leave;
            }

            //
            // we make targetEcp NULL here so we don't manually free it. it will 
            // automatically go away when the list is freed.
            //

            targetEcp = NULL;

            //
            // add the ECP to the driverContex so that it gets attached to the IRP_MJ_CREATE.
            //

            driverContext.ExtraCreateParameter = ecpList;

            //
            // intialize the attributes and issue the create.
            //

            InitializeObjectAttributes( &fileAttributes, 
                                        &fileName->Name,
                                        OBJ_KERNEL_HANDLE,
                                        NULL,
                                        NULL );

            status = FltCreateFileEx2( FltObjects->Filter,
                                       FltObjects->Instance,
                                       &fileHandle,
                                       NULL,
                                       FILE_READ_DATA | SYNCHRONIZE,
                                       &fileAttributes,
                                       &ioStatus,
                                       NULL,
                                       FILE_ATTRIBUTE_NORMAL,
                                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                       FILE_OPEN,
                                       FILE_SYNCHRONOUS_IO_ALERT,
                                       NULL,
                                       0,
                                       0,
                                       &driverContext );

            //
            // and now we need to find the targetEcp. we shouldn't try to use the
            // address from before because it's possible that someone removed the ECP
            // and freed it during processing of the IRP_MJ_CREATE and so we
            // would be touching freed memory. so we're going to see if it's
            // still in the ECP list. at this time (after FltCreateFileEx2 returns)
            // we should be the only ones using the ECP list so it's safe to do this.
            //

            ecpFindStatus = FltFindExtraCreateParameter( FltObjects->Filter,
                                                         ecpList,
                                                         &GUID_ECP_FLT_CREATEFILE_TARGET,
                                                         &targetEcp,
                                                         &targetEcpSize );

            if (ecpFindStatus == STATUS_SUCCESS) {

                NT_ASSERT(targetEcpSize == sizeof(FLT_CREATEFILE_TARGET_ECP_CONTEXT));

                //
                // add a breakpoint here to investigate the targetEcp
                //

                DbgBreakPoint();
                
                //
                // NULL it here so we don't free it.
                //

                targetEcp = NULL; 
            }
             

            DbgBreakPoint();

        } __finally {

            if (fileName != NULL) {

                FltReleaseFileNameInformation( fileName );
            }

            if (fileHandle != NULL) {

                ZwClose(fileHandle);
            }

            if (ecpList != NULL) {

                FltFreeExtraCreateParameterList( FltObjects->Filter,
                                                 ecpList );
            }

            if (targetEcp != NULL) {

                FltFreeExtraCreateParameter( FltObjects->Filter,
                                             targetEcp );

            }
        }
    }


    //
    //  See if this is an operation we would like the operation status
    //  for.  If so request it.

I've also added a function to cleanup the ECP (though please note that i'm just guessing at what needs to happen here, the documentation will explain what filters must release and when):


VOID MyECPCleanupCallback (
    __inout  PVOID EcpContext,
    __in     LPCGUID EcpType
    )
{
    PFLT_CREATEFILE_TARGET_ECP_CONTEXT targetEcp = NULL;
    
    targetEcp = (PFLT_CREATEFILE_TARGET_ECP_CONTEXT)EcpContext;

    if (targetEcp->FileNameInformation != NULL) {

        DbgBreakPoint();

        FltReleaseFileNameInformation( targetEcp->FileNameInformation );
    }

    if (targetEcp->Instance != NULL) {

        FltObjectDereference( targetEcp->Instance );
    }
}

Now, the same code works both under Win7 and Win8 (and Vista too). The FltCreateFile fails and under Win7 the targetEcp structure looks like this:

1: kd> !error @@(status)
Error code: (NTSTATUS) 0xc0000368 (3221226344) - The create operation failed because the name contained at least one mount point which resolves to a volume to which the specified device object is not attached.
1: kd> ?? targetEcp
struct _FLT_CREATEFILE_TARGET_ECP_CONTEXT * 0xd7e96f74
   +0x000 Instance         : (null) 
   +0x004 Volume           : (null) 
   +0x008 FileNameInformation : (null) 
   +0x00c Flags 

However, under Win8 we see this:

0: kd> !error @@(status)
Error code: (NTSTATUS) 0xc0000368 (3221226344) - The create operation failed because the name contained at least one mount point which resolves to a volume to which the specified device object is not attached.
0: kd> ?? targetEcp
struct _FLT_CREATEFILE_TARGET_ECP_CONTEXT * 0x9974cbfc
   +0x000 Instance         : 0x83572b18 _FLT_INSTANCE
   +0x004 Volume           : (null) 
   +0x008 FileNameInformation : 0x82144124 _FLT_FILE_NAME_INFORMATION
   +0x00c Flags            : 0
0: kd> dt 0x82144124 _FLT_FILE_NAME_INFORMATION
fltmgr!_FLT_FILE_NAME_INFORMATION
   +0x000 Size             : 0x40
   +0x002 NamesParsed      : 0
   +0x004 Format           : 2
   +0x008 Name             : _UNICODE_STRING "\Device\HarddiskVolume3\test.txt"
   +0x010 Volume           : _UNICODE_STRING "\Device\HarddiskVolume3"
   +0x018 Share            : _UNICODE_STRING ""
   +0x020 Extension        : _UNICODE_STRING ""
   +0x028 Stream           : _UNICODE_STRING ""
   +0x030 FinalComponent   : _UNICODE_STRING ""
   +0x038 ParentDir        : _UNICODE_STRING ""
0: kd> !fltkd.instance 0x83572b18 

FLT_INSTANCE: 83572b18 "PassThrough Instance" "370030"
   FLT_OBJECT: 83572b18  [01000000] Instance
      RundownRef               : 0x00000002 (1)
      PointerCount             : 0x00000001 
      PrimaryLink              : [84777bc4-83435b68] 
   OperationRundownRef      : 82c37ba8 
Could not read field "Number" of fltmgr!_EX_RUNDOWN_REF_CACHE_AWARE from address: 82c37ba8
   Flags                    : [00000000]
   Volume                   : 83435ae0 "\Device\HarddiskVolume3"
   Filter                   : 82db1a20 "PassThrough"
   TrackCompletionNodes     : 82db7588 
   ContextLock              : (83572b54)
   Context                  : 00000000 
   CallbackNodes            : (83572b64)
   VolumeLink               : [84777bc4-83435b68] 
   FilterLink               : [82db1a88-82d8403c] 

So as you can see the FileNameInformation member has the name of the target of the reparse and the Instance member points to the instance the current filter has on that volume. The documentation for FLT_CREATEFILE_TARGET_ECP_CONTEXT isn't up yet so I don't know exactly what the rules are for using this ECP. Who is supposed to release the FileNameInformation members ? When is the Volume member populated ? Is the Instance member always referenced (my research seems to indicate so but I'm not 100% sure) ?

This is a pretty big deal because all minifilters that use FltCreateFile() will need to use similar code whenever they make those calls to deal with the occasional STATUS_MOUNT_POINT_NOT_RESOLVED. This method also has the advantage that the same code can be used on any platform since Vista, and if the OS supports this functionality then the ECP will be populated. This should also make it easier in case MS decides to add support for this in Win7 SP2 or something like this.

Thursday, February 9, 2012

Problems with STATUS_REPARSE - Part II

In this post I'd like to talk about some of more complicated issues that might arise when using STATUS_REPARSE, in particular that ones that are introduced when crossing a volume boundary (when reparsing from a file on a volume to a file on a different volume).

First I'd like to talk about names (yes, the eternal problem of the file system filter developer). If a filter simply uses STATUS_REPARSE to reparse from FileA to FileB then any name queries will return the proper name of the file, FileB. So an application that queries the name of file might get a different path than the one the user actually tried to open. It's generally pretty rare that an application will open a file and then query its name so that's not really a problem but there are other applications on the system that rely on the file path to make policy decisions. For example, a firewall application might have rules that identify application by their path. If a filter redirects one those opens, even if the file contents are exactly the same one as before (like in the case of a deduplication solution) the firewall rule might not match because the path is different. So a filter that uses STATUS_REPARSE will probably end up implementing a mechanism by which to return the actual path the user tried to open. This is complicated by the fact that in some cases it is impossible to know what that name was before the reparse (see my previous post). However, assuming that a filter can know what the path the user tried to open was it can return that to functions that query the name from the file system (like IRP_MJ_QUERY_INFORMATION with the FileNameInformation class). A minifilter will also need to implement name provider functionality because just completing IRP_MJ_QUERY_INFORMATION isn't enough for that to work (see the WDK SimRep minifilter sample). Now, if the reparse crossed a volume boundary (so FileA is on Volume1 while FileB is on Volume2) then things get a bit more complicated because IRP_MJ_QUERY_INFORMATION only returns a path relative to the volume but not the actual volume name (\FileA and not Volume1\FileA). In most cases the volume is derived from the FILE_OBJECT and the filter can't really change that (and in fact since the mapping between FILE_OBJECT and the volume device and the mapping between the volume device and the drive letter are internal to the object manager (OB) a file system filter won't even see any operations at all). So it is quite likely that if FileA and FileB have different paths then a filter or even an user mode app trying to resolve the file name for the file will get a path that isn't right (like Volume2\FileA or even Volume1\FileB).

Just to point out how big this problem is, the MSDN post on Obtaining a File Name From a File Handle uses a method that gets the actual NT name for a handle by calling GetMappedFileName(). Because the handle points to the target file (Volume2\FileB) the device that is returned is the device for Volume2 (and the call to QueryDosDevice() is used to resolve this device to Volume2; btw, QueryDosDevice() is NOT the proper way to resolve a device name to a drive letter, it doesn't cover many cases and I wish MSDN would point this out). Still the point to note here is that there is nothing a filter can do to change the name the application gets (Volume2). So if the filter would actually return the original path for name queries (FileA) then the name would be Volume2\FileA which is wrong. Another API that can be used to get the file (available only since Vista though) is GetFinalPathNameByHandle() . This functions works by querying the full path like GetMappedFileName() but it gets it directly from OB and doesn't involve the memory manager and then it figures out the device to drive letter mapping, though it does it in a much more complicated way than by calling QueryDosDevice(). However it has the same problems as the previous approach because it always returns the actual volume, Volume2.

Another problem with STATUS_REPARSE when it traverses volume boundaries is that renames stop working (see my posts on renames (PartI and PartII) for a quick refresher). It is possible that some renames actually continue to work because of the MOVEFILE_COPY_ALLOWED flag, but there are cases where this flag isn't set or when the rename is issued directly via IRP_MJ_SET_INFORMATION. This is also pretty big because handling this in a filter is extremely complicated.

One other problem that STATUS_REPARSE might introduce is only specific to cross-volume reparses. I've already mentioned this in my previous post on IRP_MJ_CREATE but I'll explain again because I believe this is really important. Basically, whenever a filter calls FltCreateFile FltMgr will issue a targeted IRP_MJ_CREATE by calling IoCreateFileEx() with a device hint (or a similar mechanism in OSes where IoCreateFileEx isn't available). The problem is that if a filter below that filter tries to reparse to a different volume then the IRP_MJ_CREATE request will fail because the device hint specified by FltMgr will not be attached to that stack. As such the call to FltCreateFile will itself fail. So, in a nutshell, whenever a filter reparses to a different volume any filter above that filter cannot open any file with a path that will be reparsed by the lower filter. In my opinion this is a really big issue because it's an interop issue (so it might be hard to find during testing unless testing with a lot of filters) and because FltCreateFile is a very frequent operation in minifilters (for example most antivirus filter open files for scanning by calling FltCreateFile()). Moreover, this is not a problem most filter developers are aware of and so most filters are not written to be able to handle FltCreateFile() failing in this way. Besides there really isn't a good way to work around the issue anyway and so in practice I have yet to see any filter that actually does anything more that fail the original operation they are processing with the error returned from FltCreateFile(). I believe that is pretty much the biggest problem with STATUS_REPARSE and I haven't been able so far to come up with any good workaround. Most of the other problems I've listed require more development effort (in some cases significantly so) or some clever workarounds, but this particular issue is a blocker.

So this is it. In my experience these are the major issues (but not all the issues) that a filter must be aware of (and work around) if it implements a solution using STATUS_REPARSE. In my opinion this long list of problems makes using STATUS_REPARSE in a project pretty complicated outside of a couple of few limited scenarios. The main reason I've seen people use STATUS_REPARSE so far has been because it's very easy to use and one can have a prototype rather quickly, but I don't think it pays off in the long run.

Thursday, February 2, 2012

Problems with STATUS_REPARSE - Part I

I've seen a lot of filters lately using STATUS_REPARSE to implement some of their functionality and I wanted to talk a bit about some of the problems that using STATUS_REPARSE might cause.

I've mentioned STATUS_REPARSE a couple of times before but I wanted to quickly go over some of the details, just as a refresher. The basic idea is that during file creation (IRP_MJ_CREATE) a filter can return STATUS_REPARSE and change the FILE_OBJECT->FileName to point to a different path, in which case the status gets propagated back to the Object Manager (OB) which will retry the create with the new path. Because the new path is resolved at OB level the new file path must include the device name (either in "\Device\HarddiskVolume1" for or in "\??\C:" form, both of which are valid at OB level). There is even a minifilter sample in the latest WDK (SimRep) that shows a pretty easy way to implement a filter that redirects an open to a different file using STATUS_REPARSE. There are multiple ways in which this might be useful for a filter, in particular I've seen this used in data deduplication filters and virtualization filters.

There is another way to use STATUS_REPARSE. This generally requires a file system filter that needs to be notified by the file system when someone it trying to access a file. It is possible to tag a specific file on the file system so that when someone is trying to open the file the file system itself will return STATUS_REPARSE and the filter can watch in postCreate for STATUS_REPARSE and check if it owns the tag and if so it can perform whatever operations it wants on the file. This is a very common approach for hierarchical storage management solutions (HSM).

This technique of tagging a file can also be used by deduplication filters. For example a deduplication filter might find that fileA and fileB are identical and copy the file to a special folder (let's say it would be \SpecialFolder\UniqueFileA.bin) and then remove all the contents from FileA and FileB and tag them and store in an internal database the fact that the paths for FileA and fileB should be redirected to "\SpecialFolder\UniqueFileA.bin". Then the filter might simply wait for the file system to return STATUS_REPARSE with the right tag and then, based on the file path, it might simply update the FILE_OBJECT->FileName to point to some file under "\SpecialFolder". Of course, the filter might not need a tag in the file system and instead it might opt to check the paths for all the files in preCreate and reparse them as appropriate (actually as far as I can tell this seems to be the more popular approach).

Virtualization filters might also use this to redirect opens to a different file than the file the application or the user thinks its opening. This is fairly useful for application virtualization filters (where an application might try to open a specific configuration file in a certain location ("C:\program files\app..") but since the app is virtualized the file doesn't exist at that location and so a filter will return STATUS_REPARSE to open the file at the actual location).

So now that we have covered some of the scenarios, let's move on to some of the problem these approaches might have.

  • reparse tracking is impossible before Vista - by this I mean that it's impossible for a filter to know whether an IRP_MJ_CREATE request is the result of a STATUS_REPARSE (by the same or a different filter) or not. In other words, in the case of the deduplication filter above, when the filter sees a create for "\SpecialFolder\UniqueFileA.bin" it can't tell if the IRP_MJ_CREATE was originally sent to FileA or FileB or even if it was a direct open to \SpecialFolder\UniqueFileA.bin. Consider what happens when the user wants to do something like rename FileA. All the filter sees is a rename arrive on the FILE_OBJECT opened for \SpecialFolder\UniqueFileA.bin but it doesn't know whether this rename means FileA should be renamed or FileB should be renamed. The same applies to other namespace operations like deletes and also to all operations that modify the file contents. Please note that with the introduction of ECPs in Vista it is possible to track whether a file has been reparsed and where it has been reparsed from (if a filter sets an ECP before returning STATUS_REPARSE the ECP will be attached to IRP_MJ_CREATE on the new path).
  • split IOs - in the scenario described above, even if the filter knows what the original file that the user tried to open was (FileA or FileB), we should discuss what a filter can do when it sees a request to modify the file data. Let's say the filter gets a request to truncate the file. Clearly the request can't be allowed to happen to UniqueFileA.bin because it would then impact both FileA and FileB, but the request was only for one of them and so the other one should remain unchanged. For a deduplication filter this is a time when the deduplication should be broken (and so the UniqueFileA.bin should be copied to the original file (either FileA or FileB) and the filter should no longer reparse from that file to UniqueFileA.bin). However, this can't be done at the time when the modifying operation happens and it must be done before the file is opened, and so the only thing the filter can rely on is the information available in IRP_MJ_CREATE (such as requested access for the file) to guess whether the handle will be used for modifying the file or not. That is not very reliable but let's assume that the filter somehow can figure out exactly when to break the deduplication. There is still a problem if there are already opened handles to the file because they now point to a different file. In other words, if an application opens FileA for read-only and so the filter correctly decides that it can reparse it to UniqueFileA.bin (Handle1) and then later another application (or even the same app, doesn't matter) opens another handle for FileA for write and so the filter breaks the dedup (and so now we have Handle2), the problem is that any modification performed on Handle2 will not be seen by Handle1 because they point to different files. This is what I call Split IOs. In other words, whenever a file name changes the actual file it points to it is possible to get this type of problem, and the only way to avoid this with a filter that returns STATUS_REPARSE is to make sure that the underlying mapping to the file on a file system never changes. Also, I'd like to point out this is a pretty common scenario. During my university days I used to have homework along the lines of "implement a producer-consumer solution where there are 10 consumer processes and 1 producer process and they all use a file for communication" and naturally if I had such a filter on my system all the 10 consumer processes would point to UniqueFileA.bin and the producer would point to FileA and so the consumers would never see the producer data… Also, please note that once there are handles that are supposed to point to the same file but point to different files it's not just reads and writes that are going to be out of sync, but also locks and oplocks and many other file system semantics might be broken. This is a very serious issue because it can lead to security issues (what if an antivirus thinks it scans FileA (but instead scans UniqueFileA.bin) and it finds it clean and allows the IRP_MJ_CREATE to continue but then the actual request ends up on a different file that is malicious?), data corruption or data loss.
  • FileIDs are more complicated - please see my previous post on STATUS_REPARSE and fileIDs.
  • Performance is generally not great - in most cases using STATUS_REPARSE requires dealing with names and querying the name in preCreate is a big performance hit. Also, looking up names in internal databases is generally done using normalized names, which are also a bigger performance hit (so normalized names in preCreate are the worst in terms of performance). Finally, if the architecture of the product requires getting a normalized name in preCreate and because filters that need to run on XP or Server 2003 can't tell when a reparse has happened it is often the case that filters get the normalized name twice (in our example, for FileA when they determine they must reparse to "\SpecialFolder\UniqueFileA.bin" and then for the subsequent IRP_MJ_CREATE for \SpecialFolder\UniqueFileA.bin, where the filter would determine that no reparse is necessary (but it would still query the name to make that determination)). This can make this type of filters have a pretty significant performance hit.

This is pretty dense material so I'll leave the other issues I was going to talk about for next week.